Circuit Design with VHDL

Third Edition

Volnei A. Pedroni

The MIT Press

Cambridge, Massachusetts

London, England

© 2020 Massachusetts Institute of Technology

All rights reserved. No part of this book may be reproduced in any form by any electronic or mechanical means (including photocopying, recording, or information storage and retrieval) without permission in writing from the publisher.

Library of Congress Cataloging-in-Publication Data

Names: Pedroni, Volnei A., author.

Title: Circuit design with VHDL / Volnei A. Pedroni.

Description: Third edition. | Cambridge, MA : The MIT Press, [2019] | Includes bibliographical references     and index.

Identifiers: LCCN 2018050941 | ISBN 9780262042642 (hardcover ; alk. paper)

Subjects: LCSH: VHDL (Computer hardware description language) | Electronic circuit design. | System design.

Classification: LCC TK7885.7 .P43 2019 | DDC 621.39/5--dc23

LC record available at https://lccn.loc.gov/2018050941

d_r0

Preface

This text offers a comprehensive coverage of VHDL and its applications to the design of digital circuits. It is a completely updated and expanded version of its highly successful first and second editions, now including all VHDL-2008 constructs and several new features, such as an extensive review of digital circuits, RTL analysis, and a superior collection of VHDL design examples and exercises. It is suitable for undergraduate and graduate courses on VHDL and digital circuits design in the areas of electrical/computer engineering and computer science, in-house courses on VHDL and digital circuits design, and engineers and other VHDL practitioners in the industry. The only background knowledge needed to follow the book is familiarity with basic digital logic concepts and circuits. Because of the tight combination of digital circuits analysis, detailed VHDL code, and industry-standard examples and exercises, this text is also recommended for digital VLSI courses and digital VLSI designers in general.

Before entering VHDL (chapters 5–18), a review of digital circuits is presented (chapters 1–4), resulting in a self-contained text that allows the teaching of digital circuits design with VHDL using a single reference. To complete this self-containment, all tutorials needed for synthesis and simulation (with Xilinx and Intel FPGAs) are included in the appendixes. The review chapters cover all classes of digital circuits, which consist of combinational logic circuits, combinational arithmetic circuits, sequential circuits, and sequential circuits modeled as finite state machines. A review of FPGAs is also included. Full VHDL-2008 coverage is presented, while still keeping the text as concise as possible. The same acclaimed sequence of topics in the previous two editions was maintained, with updated descriptions and modern examples carefully planned to make the understanding of the language simple and enjoyable.

Additionally, a clear separation (unusual elsewhere) between code that is for synthesis versus code that is for simulation is made. In summary, everything in chapters 5–17 is synthesizable, while the topics that are specific for simulation are concentrated in chapter 18. The VHDL codes in the examples are always complete (also unusual elsewhere), and not just partial sketches. Moreover, simulation results and comments are also offered.

The collections of examples and exercises were hugely expanded and include many not seen before in the literature. That was possible because of the review chapters, which present the necessary background and, at the same time, keep it separated from the VHDL chapters, so that a neater and more profound coverage results. Further, an enormous effort was devoted to making the examples and exercises interesting to students, with very illustrative physical demonstrations, which are crucial for their motivation. Finally, RTL analysis and discussions on hardware optimization are also included in many of the examples and exercises in this edition of the book. For that, too, the review chapters were fundamental.

Acknowledgments

I am grateful to the many people, in so many countries, who helped to make the previous editions of the book so successful. Their comments (along the years) were carefully considered and incorporated into this new edition when appropriate.

In particular, I am most grateful to the reviewers of this third edition for their professional comments and suggestions.

Finally, my dearest thank you to my two electrical engineering sons, Bruno and Ricardo Pedroni, for the countless fun and productive technical discussions on the many aspects of this material.

1    Review of Combinational Circuits

1.1    Combinational Circuits

This chapter presents a review of combinational circuits and binary arithmetic.

By definition, combinational circuits are those whose outputs depend solely on the present inputs; in other words, the outputs do not depend on previous circuit states. A regular adder, for example, is combinational because the result of the present computation does not depend on previous computations.

By contrast, sequential circuits (reviewed in chapter 2) are those for which previous circuit states matter. For example, a counter is sequential because the next state depends on the state at which the counter is now. Sequential circuits, except for rare cases (asynchronous-logic based systems, for example), are characterized by the presence of a clock signal, which does not occur in combinational circuits.

Combinational circuits can be divided into logic and arithmetic combinational circuits. If sign matters, it is arithmetic; otherwise, it is logic. For example, AND gates and multiplexers are logic, while adders and most comparators are arithmetic.

Notes:

1)  About numeric values:    In VHDL, a single bit is written in single quotes (examples: '0', '1'), bit vectors are written in double quotes (examples: "110", "00000"), and integers are written without quotes (examples: 110, 9, −255). For simplicity, quotes were omitted in the review chapters (1 to 4), but the reader will be able to easily determine the proper values from the context.

2)  About signal and constant names:    In the review chapters (1 to 4), short names were employed, which is particularly important for the figures. In the VHDL chapters (5 to 18), longer, more meaningful names are also employed, following the suggestions of section 5.8.

1.2    Fundamental Logic Gates

We start by briefly reviewing the ten fundamental logic gates, all included in figure 1.1. For each gate, the figure shows the symbol, the boolean equation, and the construction using CMOS logic.

Figure 1.1
Fundamental logic gates.

CMOS stands for Complementary MOS because for each nMOS transistor (i.e., an n-channel MOSFET) there is a pMOS transistor (a p-channel MOSFET). The former appears in the lower part of each circuit, while the latter is in the upper part. The former conducts (i.e., is turned on) when a 1 (i.e., a positive voltage, like 1.8V) is applied to its gate, while the latter conducts when a 0 (zero volts) is applied to it. Taking the inverter, for example, which is the simplest gate, y = 0 occurs when a = 1 because then the nMOS transistor connects the output node to ground, while y = 1 occurs when a = 0 because then the pMOS transistor connects the output node to the upper power supply rail, VDD.

The next circuit is the NAND gate, here with two inputs, therefore with two nMOS transistors in series and two pMOS transistors in parallel; consequently, only when all inputs are high the output will be low, and that is the reason for its name. Its noninverting version, the AND gate, is shown next; it requires a full NAND plus an inverter, being therefore more space and power consuming, and also slower (the inverter adds extra propagation delay), than the NAND gate. An analogous behavior occurs in the NOR and OR gates, which are the mirrored versions of the NAND and AND gates, respectively.

The next pair is formed by the XOR and XNOR gates. The former produces y = 1 when the number of inputs that are high is odd, while the latter outputs y = 1 when that number is even. For the particular case of two inputs, the XOR gate is used to detect whether the inputs are different, while the XNOR detects whether they are equal.

The next gate is the regular buffer, used, for example, at the inputs and outputs of digital integrated circuits (in the connections between the chip’s internal circuitry and the chip pins). At the input, it reconditions the incoming signal and is also responsible for voltage scaling; for example, many chips operate internally with 1V logic, but the incoming pulses might employ other logic voltages, like 1.8V, 2.5V, or 3.3V, leaving to the buffer the down conversion. At the output, the buffer must do the opposite; additionally, a stronger output (with higher current or fan-out capability) might be required, which is usually achieved by means of cascaded stages of growing size, as illustrated in the figure.

The next gate is the tri-state buffer. Note that when ena = 0 both transistors connected to the y node are turned off, leaving that node literally in a “floating” condition. This allows that node to be connected to a wire shared by several circuits, as far as only one tri-state buffer is turned on at a time. An application example is in computer buses, which can then be shared by several peripherals.

The final gate is a pass switch, which is somehow analogous to the tri-state buffer, but it is not buffered and can transfer even analog voltages. Its construction consists simply of two transistors (nMOS and pMOS) in parallel. An alternative construction is to use just one transistor, but that would be slower, with a smaller output voltage swing, and with a poor 0 or a poor 1, depending on whether the pMOS or the nMOS transistor is maintained, respectively.

1.3    Chain-Type versus Tree-Type Structures

As will be seen ahead, many combinational circuits can be implemented according to either one of the architectures depicted in figure 1.2, where xi (i = 0 to M − 1, M being the number of inputs, equal to 8 in this example) represents the inputs, y is the output, and the circles are arbitrary processing elements.

Figure 1.2
(a) Chain-type (linear delay) and (b) tree-type (log delay) structures for combinational processing arrays.

The chain-type case (figure 1.2a) is simpler, but its propagation delay is linearly dependent on M (there are M − 1 processing elements in series). The tree-type case (figure 1.2b) is slightly more complicated when M is not a power of two, but its propagation delay is just logarithmically dependent on M; more specifically, it is proportional to the number of tree layers, L = ⎡log₂M⎤. For example, for M between 9 and 16, the delay in the former is between 8 and 15 delay units, while in the latter it is just 4 delay units.

These architectures will be useful in the description of several of the combinational circuits in the sections that follow.

1.4    Examples of Combinational Logic Circuits

As mentioned in the introduction, combinational circuits can be divided into logic and arithmetic combinational circuits. If sign matters, it is arithmetic; otherwise, it is logic. We review logic circuits here, proceeding to arithmetic circuits in the next section. Five cases were selected for review here: 1) multiplexer, 2) address decoder, 3) parity detector, 4) priority encoder, and 5) binary-to-BCD converter.

1.4.1    Multiplexer

Multiplexers (or simply “muxes”) are among the most common combinational circuits. They act as data routing switches, needed in many applications. In a microcontroller, for example, different instructions are essentially just different setup sequences (cadenced by clock pulses) for its many multiplexers.

The symbol for a 4-input multiplexer is shown on the left of figure 1.3a, followed by a conceptual circuit, constructed with switches, and finally the corresponding truth table. The circuit has four data inputs (a to d), an input-select port (sel), and one output (y). When sel = 0, the upper input is connected to the output, so y = a; when sel = 1, the output is y = b; and so on.

Figure 1.3
Multiplexer.

A completely generic mux is depicted in figure 1.3b, now with inputs a(0) to a(M − 1) and output b. The number of inputs is then M, each with N bits, so the output must also have N bits because it will receive one of the input signals. The number of bits in the port-select signal is S = ⎡log₂M⎤; for example, for M between 5 and 8, S = 3 bits are needed.

1.4.2    Address Decoder

One of the most common decoders is the address decoder, which receives this name because it is employed in memory chips to activate the word line corresponding to the received address vector.

The symbol for an address decoder is presented in figure 1.4. The inputs are the address vector (a) plus an enable signal (ena). The output (b) is the signal that will activate one of the memory lines, so only one bit is different from all the others at a time (in this example, the distinct bit is 1, but it could be 0). Therefore, this circuit is just a binary-to-one-hot code converter, as can be clearly observed in the accompanying truth table. The only exception occurs when ena = 0 because then none of the memory words should be selected. If the number of bits in a is N, then the number of bits in b is 2N.

Figure 1.4
Address decoder.

1.4.3    Parity Detector

A parity detector is a circuit that detects whether the number of 1s in a binary vector is odd or even. Two implementation examples, employing 2-input XOR gates, are shown in figure 1.5. They compute y = x₀ ⊕ x₁ ⊕ … ⊕ xN₋₁ (N = 10 in this example), producing y = 1 when the parity of x is odd.

Figure 1.5
Parity detector: (a) Chain-type (linear delay) and (b) tree-type (log delay) constructions.

Recall the chain- versus tree-type structures seen in figure 1.2, which correspond to the cases in figures 1.5a and b, respectively. In the former, the propagation delay is linear (there are N − 1 XOR gates in series), while in the latter it is logarithmic (there are L = ⎡log₂N⎤ XOR layers now).

1.4.4    Priority Encoder

Priority encoders are used to manage accesses to shared resources. If two or more requests are received, the role of a priority encoder is to inform which of the requests should be served. In the description that follows, it is assumed that the priority grows toward the MSB (most significant bit).

The functional description of a simple priority encoder is presented in figure 1.6a. As shown in the truth table, if there are no requests, none of the outputs are asserted; however, when there are requests, only one output is asserted, corresponding to the input with highest priority. Looking at this truth table, we conclude that this circuit too produces a one-hot code at the output (except for the no-request case). (Another priority-encoding option is to produce the address of the winning input.)

Figure 1.6
A priority encoder: (a) Functional description; (b) Chain-type construction (linear delay); (c) Tree-type construction (log delay).

Two constructions are depicted in figures 1.6b and c, again resembling the chain-and tree-type architectures of figure 1.2, resulting linear and log propagation delays, respectively.

1.4.5    Binary-to-BCD Converters

Figure 1.7 depicts a common situation in which a binary string must be decomposed into decimal digits for human reading (in a calculator, for example). In this example, we need to display the decimal value corresponding to a 12-bit unsigned string, hence corresponding to integers in the 0 to 2¹² − 1 = 4095 range, thus requiring four BCD (binary-coded decimal) digits. For the input value 100011101111 given in the figure, the expected output is BCD(3:0) = 2287. Note that this has nothing to do with the display itself, which can be of any type and is taken care of by the display interface. Two solutions for this problem are described next.

Figure 1.7
The need for binary-to-BCD conversion.

Division-based binary-to-BCD converter    The first solution, shown in figure 1.8, is based on unsigned integer dividers, where q and r are the quotient and remainder, respectively. In this example, bin has N = 12 bits, but note that the size of the dividers can be reduced as we go down the line. Also, since the last output (q₂) has only 3 bits, the MSB of BCD3 is zero. The remainders plus the last quotient constitute the BCD digits; i.e., BCD(3:0) = q₂r₂r₁r₀. Even though very simple, this solution is forbiddingly expensive in terms of hardware (exercise 11.7), so it is not recommended.

Figure 1.8
Division-based binary-to-BCD converter (a bad idea).

Double-dabble binary-to-BCD converter    A better approach is to use the double-dabble algorithm. The algorithm is illustrated in figure 1.9 for N = 8 bits, so the corresponding decimal value can be anywhere in the 0-to-255 range, which implies three BCD digits. The algorithm can be summarized as follows: one bit at a time is shifted in (left shift), starting with the MSB; after each shift, if a BCD group is bigger than 4, then 3 is added to it, except after the last shift. The input shown in the figure is 11101101 (= 237), so BCD2 = 2, BCD1 = 3, and BCD0 = 7 are expected at the output, which indeed occur.

Figure 1.9
Double-dabble algorithm (illustrated using bin with N = 8 bits).

A general construction approach for the double-dabble algorithm is presented in figure 1.10a. In this case, bin has N = 9 bits (the dark box at the top filled with x’s), hence ranging from 0 to 511, which implies again three BCD digits. Since each BCD digit requires 4 bits, a total of 12 bits are involved, so 3 zeros are included on the left of bin (the dark box at the top filled with 0s). A conditional adder (CondAdder) adds 3 to its input when the input is higher than 4.

Figure 1.10
(a) Construction principle for the double-dabble algorithm (illustrated for bin with N = 9 bits); (b) numeric example, for N = 12 bits, with bin = 111111111111 (= 4095).

The placement of the CondAdder blocks is as follows: start by placing CondAdder 1 under the rightmost bit of the all-zero vector and under the three leftmost bits of bin; CondAdder 2 to 6 are placed one position to the right of the preceding block, until the second LSB (least significant bit) of bin is reached; after that, the next pile is constructed, starting with CondAdder 7, placed one position to the left of CondAdder 1, followed by CondAdder 8 to 9, and ending when the second pile reaches the bottom level of the first pile. The number of piles depends on which bits of the last BCD digit are subject to change; in this example, the largest value of BCD2 is 5, so three of its bits are subject to change; since the second pile already covers those three bits of BCD2 (via CondAdder 7), a third pile is not needed. The reason for placing CondAdder 1 in that position is that at least three bits of bin are needed for the input to be bigger than 4, and the reason for placing CondAdder 6 one position before the last LSB of bin is that the last bit never changes. This procedure can be easily extended to any number of bits.

A detailed numeric example, for N = 12 and following the construction procedure just described, is presented in figure 1.10b. Since the largest value with 12 bits is 4095, the largest value of BCD3 is 4, which means that three of its bits are subject to change, so the CondAdder blocks must cover those bits, which implies that a third pile is now needed. The input in this example is bin = 111111111111, resulting BCD(3:0) = 4095 at the output.

Note:    When the string to be converted to BCD comes from a counter (figure 1.11), the binary-to-BCD conversion can be easily obtained by breaking the counter down to subcounters, each counting from 0 to 9 (or to some other value in this range), so the output of each subcounter is a BCD digit automatically. In this case, the second counter is incremented every time the first counter returns to zero, the third counter is incremented when both previous counters return to zero, and so on. This is the least expensive binary-to-BCD conversion, costing just a few extra flip-flops and comparators, but it is not a general solution (and the circuit is not combinational).

Figure 1.11
BCD conversion when the signal to be converted is produced by a counter (a particular case).

1.5    Examples of Combinational Arithmetic Circuits

As mentioned in the introduction, combinational arithmetic circuits are those for which sign matters. The following nine cases were selected for review here: 1) full-adder (FA) unit, 2) carry-ripple adder, 3) faster adders (Manchester, carry-lookahead, and Kogge-Stone tree), 4) adder arrays, 5) subtracters, 6) incrementer, decrementer, and two’s complementer, 7) parallel multiplier, 8) comparators (equality and greater-than/equal-to), and 9) arithmetic logic unit (ALU).

1.5.1    Full-Adder Unit

A full-adder (FA) unit is depicted in figure 1.12a, where a and b represent the input bits to be added, cin is the carry-in bit (to be also added to a and b), sum is the sum bit, and cout the carry-out bit. As shown in the truth table of figure 1.12b, sum must be high whenever the number of inputs that are high is odd (odd-parity function), while cout must be high when two or more inputs are high (majority function); therefore, sum = a ⊕ b ⊕ cin and cout = a.b + a.cin + b.cin. A traditional 28-transistor implementation, using CMOS logic, is shown in figure 1.12c.

Figure 1.12
FA unit: (a) Circuit ports; (b) Truth table; (c) Transistor-level implementation using CMOS logic.

The FA unit is a fundamental building block for several arithmetic circuits, like the carry-ripple adder, described next.

1.5.2    Carry-Ripple Adder

The simplest multi-bit adder, which performs the traditional addition operation illustrated in figure 1.13a (where N = 4 bits), is the carry-ripple adder, shown in figure 1.13b. It consists of N FAs connected directly through their carry-out/carry-in ports. The input is formed by the addends, a(N − 1:0) and b(N − 1:0), plus the carry-in bit, cin, while the output consists of the sum, s(N − 1:0), plus the carry-out bit, cout (notice in figure 1.13a that c4 can play the role of s4, but only because this adder is unsigned). Because the carry must propagate (ripple) through all stages (without any special arrangement for it), this adder is slightly slower than others, like those described next.

Figure 1.13
(a) Addition operation and (b) carry-ripple adder.

1.5.3    Faster Adders (Manchester, Carry-Lookahead, and Kogge-Stone Tree)

The development of faster adders usually makes use of two signals, called generate and propagate, given by Gi = ai ● bi and Pi = ai ⊕ bi, where ai and bi are the ith bits of vectors a and b (the addends), respectively. If ai = bi = 1, carry-out is guaranteed to occur, so it should be generated immediately (that is the reason for Gi = 1 in such a situation). On the other hand, if only one of them is 1, the carry-out bit will be equal to the carry-in bit, so the latter should be propagated immediately to the next stage (that is the reason for Pi = 1). The importance of G and P resides on the fact that neither depends on carry-in bits, so they can be computed immediately.

Figure 1.14 shows some examples of fast adders. First, note in all the presence of G and P signals. In the Manchester carry-chain adder (figure 1.14a), the focus is on reducing the propagation delay in the critical path (dashed line in the figure). In the carry-lookahead adder (figure 1.14b), a totally different approach is taken; instead of waiting for the carry-in bit produced by preceding stages, each stage produces its own carry-in bit (note the c₁,c₂, … blocks). This technique, however, is only fine for few bits (say, 4 or 5) because the fan-in of the carry stages grows as the number of bits grows (note the number of inputs to the c₄ block), causing the internal circuitry to be slow and hence offsetting the previous gain in terms of delay reduction. The final example (figure 1.14c) is a Kogge-Stone tree adder (for N = 16 bits); the squares in the upper part are Gi and Pi calculators; the circles in the center compute Gi:j (gray) or Gi:j plus Pi:j (black); and the triangles at the bottom calculate the sum (XOR gates). In this arrangement, the number of layers is just ⎡log₂N⎤ + 2, which reduces the total propagation delay.

Figure 1.14
Faster adders: (a) Dynamic carry-chain Manchester adder; (b) Carry-lookahead adder; (c) Kogge-Stone tree adder.

1.5.4    Adder Arrays

When not two but several values must be added simultaneously, an array of (two-input) adders is employed. Two options are shown in figure 1.15, where M (= 8) signals, represented by ai (i = 0 to M − 1), are added, producing sum at the output. Note that these architectures, called chain-type adder array (figure 1.15a) and tree-type adder array (figure 1.15b), are similar to those seen in figure 1.2. The resulting propagation delay is linear in the former (M − 1 = 7 adders in series) and is logarithmic in the latter (L = ⎡log₂M⎤ = 3 adders in series, where L the number of tree layers).

Figure 1.15
Adder arrays: (a) Chain type (linear delay) and (b) tree type (log delay).

Figures 1.15a and b show also the number of bits needed at the output of each adder in case overflow must be prevented. Note that only the tree type works with minimal-size adders (for example, only one adder has size N + 3 in that architecture).

Having the number of bits under-limited in the internal branches or at least at the output is sometimes necessary (for example, in digital filters, sections 2.11.6 and 2.11.7, with normalized gain). In such cases, preserving the relative values of the accumulated sums (with some distributed error due to truncation) might be more important than having their absolute values (with large errors due to saturation). Such techniques (truncation, rounding, and saturation) are reviewed in section 1.6.4.

1.5.5    Subtracters

Subtracters are constructed by simply combining a two’s complementer with a regular adder. A circuit of this type is depicted in figure 1.16, which allows two operations: a + b and a − b. When opcode = 0, the outputs of the XOR gates are equal to the inputs, and cin = 0, so regular addition occurs. On the other hand, when opcode = 1, the XOR gates invert the inputs, and since now cin = 1, b′ + 1 = −b results (i.e., b gets two’s complemented); consequently, the circuit now computes a − b.

Figure 1.16
Adder plus two’s complementer, computing s = a ± b.

1.5.6    Incrementer, Decrementer, and Two’s Complementer

Three special arithmetic circuits are described in this section.

Incrementer:    To increment a number (that is, to add 1 to it), a regular adder, with 1 connected to one of its inputs, could be used. However, if we look at the boolean equations in that particular situation, we arrive at a much simpler circuit, shown in figure 1.17a (for 5 bits in this example). Its output is b = a + 1.

Figure 1.17
(a) Incrementer; (b) Decrementer; (c) Two’s complementer.

Decrementer:    A similar behavior occurs in a decrementer. To subtract 1 from a number is the same as to add 11 … 1 to it, which leads to the circuit of figure 1.17b. Its output is b = a − 1.

Two’s complementer:    Another circuit that falls in this category is the two’s complementer, needed to represent negative numbers. It can be implemented as seen in figure 1.16, but if the adder is not to be involved, the circuit of figure 1.17c can then be used.

1.5.7    Parallel Multiplier

A truly parallel multiplier is depicted in figure 1.18. The circuit receives two N-bit non-negative integers (N = 4 in this example) and computes their 2N-bit product. The circles with a “+” sign inside are FA units (section 1.5.1), so the entire computation is based on AND gates and FA units.

Figure 1.18
Parallel multiplier.

1.5.8    Comparators (Equality and Greater-than/Equal-to)

Comparators are arithmetic circuit because sign matters. For example, say that we need to compare 1000 to 0111; if the system is unsigned, the values are 8 and 7, so the first is bigger, but if the system is signed, they are −8 and 7, so then the second is bigger. The only exception is the equality (or inequality) comparator, in which sign obviously is not an issue.

Two examples are depicted in figure 1.19 (for N = 4 bits). The first is an equality comparator, while the second is an equality comparator plus a greater-than/equal-to comparator. An important conclusion from these circuits is that it is cheaper (less hardware) to compute equality (or inequality) than to compute relativity (i.e., a = b and a ≠ b, which have the same cost, are cheaper than a > b, a ≥ b, etc.).

Figure 1.19
Comparators of (a) equality and (b) greater-or-equal plus equality.

1.5.9    Arithmetic Logic Unit

ALU (arithmetic logic unit) circuits are at the core of any microprocessor or microcontroller. As the name says, it is a circuit that is capable of computing several logic as well as arithmetic functions.

A simple ALU is depicted in figure 1.20a, having a, b, cin (carry-in), and opcode (operation code) as inputs, and result, cout (carry-out bit), and oflow (overflow flag) as outputs. The desired functionality is expressed in the truth table of figure 1.20b, where each function is selected by a different value of opcode. The upper eight instructions are logic, while the lower eight are arithmetic. The number of bits in a, b, and result is assumed to be the same, so in the last six operations the carry-out bit and/or the overflow flag (section 1.6.1) might need to be set.

Figure 1.20
Arithmetic logic unit (ALU).

1.6    Binary Arithmetic

We close this chapter by reviewing fundamental concepts related to binary arithmetic, needed later in VHDL designs.

1.6.1    Carry Bit and Overflow Flag

We start by recalling the definitions for carry and overflow. It is not uncommon to hear that “the carry flag is for unsigned arithmetic, while the overflow flag is for signed arithmetic,” among other variations, which might even include a third bit, called sign flag. The definitions adopted here are more meaningful (and more useful) than that, and they are exactly the same for unsigned and signed arithmetic.

They can be better understood with the help of figure 1.21a, where two 4-bit values (a and b) are added, producing a 4-bit sum (s₀ to s₃; bit s₄ is optional). Note also bits c₀ to c₄; the first (c₀) is a carry-in bit, which might or might not exist, while the others (c₁ to c₄) are the internal carry values produced during the summations. The need for cin arises when a larger adder (say, with 8 bits) needs to be constructed using smaller adders (with 4 bits, for example), in which case cout (c₄) of one stage can be connected directly to cin (c₀) of the next, as in figure 1.21b. Notice that cout is used in the interconnection(s), while oflow is produced only in the final stage; when s₈ (the new MSB) is used, oflow is usually not implemented.

Figure 1.21
Carry, overflow, and new MSB in addition; (b) Cascaded adders.

The definitions are then as follows:

Carry flag (cout): It is simply the last internal carry bit (so cout = c₄ in figure 1.21a) produced during the summations, calculated according with the table in figure 1.12b (for negative numbers or subtraction, the two’s complement value must be used). This flag is used for adder cascading (explained above) and for determining the other flags/bits (oflow and s₄).

Overflow flag (oflow):    It is a flag that is raised when the result does not fit in the original N bits (s₃-s₀ in figure 1.21a). In other words, oflow = 1 means that the result is not correct if read without the fifth bit (s₄).

Carry-based sum bit (s₄ in figure 1.21a):    This bit, which is optional and becomes the result’s new MSB when employed, is such that the result (sum) is always correct if read with that bit included.

These definitions will be used in the next two sections, where equations and numeric examples are presented.

As a final comment, notice that carry in fact is not a flag, but rather part of the result (so carry bit would be a better designation for cout than carry flag). A flag is a warning, or alarm, informing that something happened, so overflow is a flag.

1.6.2    Unsigned Integer Arithmetic

An N-bit unsigned integer ranges from 0 to 2N − 1; for example, if N = 4, the range is 0 to 15. To extend the size of an unsigned integer without changing its value, zeros must be included on its left (called zero padding). For example, 1111 would become 0 … 01111.

To add two N-bit unsigned integers, N + 1 bits are needed for the sum if overflow is to be prevented. However, it is common in digital circuits to use the same size for all (input and output) signals, so an overflow flag might be required. Criteria for determining the carry-out bit, the overflow flag, and the sum’s new MSB (based on their definitions presented above) are summarized below.

Carry-out bit (cout):    It is equal to the last internal carry bit. It can also be determined by looking only at the operands; if both MSBs are 1, or if they are different but the sum’s MSB is 0, then cout = 1.

Overflow flag (oflow):    In unsigned integer adders, oflow = cout.

Sum’s new MSB [s(N)]:    In unsigned integer adders, s(N) = cout.

Reverse equation [find cout, given s(N)]:    In unsigned integer adders, cout = s(N).

The equations resulting from the comments above are summarized in the table of figure 1.22a and the corresponding top-level circuit diagram is shown in figure 1.22b. Numeric examples are presented in figure 1.23.

Figure 1.22
(a) Equations for determining the carry-out bit, the overflow flag, and the sum’s new MSB in unsigned and signed addition; resulting (b) unsigned and (c) signed circuits.

Figure 1.23
Numeric examples for unsigned addition.

For multiplication, 2N bits are needed when employing two N-bit integers. For the particular case of multiplication by a power-of-two integer, logic shifts to the left suffice; for example, to multiply by 2k, the vector must be shifted k positions to the left (after zero padding). However, if a result with only N bits is wanted, the result is correct only if just zeros are dropped during the shifts.

For division of two N-bit integers, N or 2N bits are needed, depending on whether only the quotient or both the quotient and the remainder are wanted, respectively. Analogously to multiplication, division by a power of two can be done with simple logic shifts to the right.

1.6.3    Signed Integer Arithmetic

An N-bit signed integer ranges from −2N⁻¹ to 2N⁻¹ − 1; for example, if N = 4, the range is −8 to 7. To extend the size of a signed integer without changing its value, the MSB must be repeated on its left (called sign extension); for example, 0111 becomes 0 … 0111, while 1000 becomes 1 … 1000.

Negative numbers are usually represented using two’s complement format, so to subtract a number from another, all that is needed is to two’s complement it and then to add it to the other number. From a circuit perspective, the easiest way to obtain the two’s complement is to flip all bit values and then add 1 to the result (recall the circuit seen in figure 1.16).

From a human perspective, a simpler way is, given a negative decimal, to simply add it to 2N; for example, to obtain the representation for −13, with N = 5 bits, we just write the vector for 2⁵ − 13 = 19 (= 10011), so −13 = 10011. To go the other way (i.e., to find the decimal value of a negative binary value), we can obviously do the opposite of above, or we can consider the zeros as the significant digits and then add 1 to the result, plus the minus sign; for example, the decimal value of 10011, lookind at the zeros, is −(12 + 1) = −13.

To add two (positive or negative) N-bit signed integers, again N + 1 bits are needed for the sum if overflow is to be prevented. However, if only N positions are available, overflow can occur. Criteria for determining the carry-out bit, and the overflow flag, and the sum’s new MSB (based on their definitions above and with negative values entered in two’s complement format) are summarized below.

Carry-out bit (cout):    It is equal to the last internal carry bit. It can also be determined by looking only at the operands: if both MSBs are 1, or if they are different but the sum’s original MSB is 0, then cout = 1.

Overflow flag (oflow):    Overflow can be detected by looking only at the carry bits; if the last two carry bits are different, then oflow = 1. Overflow can also be detected by looking only at the operands; if their MSBs are equal but the sum’s original MSB is different from them, then oflow = 1.

Sum’s new MSB [s(N)]:    If the MSBs of the two operands are equal, s(N) is equal to cout; if they are different, cout must be inverted to produce s(N).

Reverse equation [find cout, given s(N)]:    If the MSBs of the two operands are equal, cout is equal to s(N); if they are different, s(N) must be inverted to produce cout.

The equations resulting from the comments above are summarized in the table of figure 1.22a and the corresponding top-level circuit diagram is shown in figure 1.22c. Numeric examples are presented in figure 1.24.

Figure 1.24
Numeric examples for signed addition.

Multiplication and division using left and right shifts, respectively, are similar to what was described for unsigned integers, except that division is performed with arithmetic shift instead of logic shift (i.e., instead of zeros, the empty positions are filled with the leftmost bit value). This is illustrated in figure 1.25a, for the positive value 00101 (= 5), and in figure 1.25b, for the negative value 11010 (= −6). The result can be incorrect in left shifts; it only did not occur in figures 1.25a and b because the values were conveniently chosen.

Figure 1.25
Multiplication- and division-by-two by means of left or right shift, respectively, for (a) positive and (b) negative integers.

Simply speaking, fixed-point arithmetic is just a scaled version of integer arithmetic, so the difference in hardware consumption for implementing one versus the other is usually small. Nevertheless, operations like truncation plus rounding are more sophisticated (and produce better results) in fixed-point, so they are also more complex to implement. Such operations are described next.

1.6.4    Extension, Truncation, Rounding, and Saturation

Extension of integers    Extension (hence N > M, where M is the original vector’s size and N is the new vector’s size) can be done on the left or on the right. Extension on the left is illustrated in figure 7.3; for unsigned integers it consists of placing zeros on the left of the original vector, while for signed integers the MSB is replicated on the left (hence in both cases the original value remains unchanged). Examples are shown below, for M = 5 and N = 7.

Unsigned extension on the left: 10010 (18) → 0010010 (18)

Signed extension on the left: 10010 (−14) → 1110010 (−14)

Extension on the right consists of including N − M zeros on the right of the original vector. This is the same as shifting the vector N − M positions to the left (without loss of data), which causes all values to be multiplied by the same constant (2N⁻M), so the relative values stay the same. Examples are shown below, again for M = 5 and N = 7. Notice that the values do get multiplied by 2² = 4.

Unsigned extension on the right: 10010 (18) → 1001000 (72)

Signed extension on the right: 10010 (−14) → 1001000 (−56)

Truncation of integers    Truncation (hence N < M) too can be done on the left and on the right. Truncation on the left is illustrated in figure 7.3; for unsigned integers the M − N leftmost bits are eliminated, while for signed integers the M − N bits that follow the leftmost bit are eliminated. Examples are shown below, for M = 7 and N = 5. Note that for unsigned the value only remains the same if all eliminated bits are zero, while for signed the value remains the same only if all eliminated bits are equal to the MSB.

Unsigned truncation on the left: 0011010 (26) → 11010 (26); 0111010 (58) → 11010 (26)

Signed truncation on the left: 1111010 (−6) → 11010 (−6); 1001010 (−54) → 11010 (−6)

Truncation on the right consists of eliminating the M − N rightmost bits. This is the same as shifting the vector to the right M − N positions, which causes all values to be divided by the same constant (2M⁻N), so the relative values stay the same (except for the error due to the discarded bits, of course). Examples are shown below, again for M = 7 and N = 5. Notice that the values do get divided by ~2² = 4.

Unsigned truncation on the right: 1000111 (71) → 10001 (17)

Signed truncation on the right: 1000111 (−57) → 10001 (−15)

Truncation on the left is useful when the values are expected to always fit in a shorter (N-bit) vector, while truncation on the right is useful when that is not expected and successive computations are involved, being the relative results of such computations more important than their absolute values (the absolute values would be subject to large errors). Note that in the latter the original values can be recovered (approximately) using extension on the right, explained above.

Truncation of fixed-point data    Consists of eliminating part of the rightmost bits. It is usually followed by a rounding procedure (explained below), being that listed in line 6 of table 1.1 the recommended option. Examples that truncate the fraction from 5 bits to 3 bits are shown below, for unsigned data, with the rounding procedure just mentioned included in the last step.

100.01101 (4.40625) → 100.011 (4.375) → 100.011 + 0.000 = 100.011 (4.375)

100.01111 (4.46875) → 100.011 (4.375) → 100.011 + 0.001 = 100.100 (4.500)

Table 1.1

Main rounding schemes

Type

Designation

Other designations

Examples

Direct rounding

1) round up

round toward plus infinity, ceiling

  3.1 to 3.9 → 4 −3.1 to −3.9 → −3

2) round down

round toward minus infinity, floor

  3.1 to 3.9 → 3

−3.1 to −3.9 → −4

3) round toward zero

round away from infinity, truncate

  3.1 to 3.9 → 3

−3.1 to −3.9 → −3

4) round away from zero

round toward infinity

  3.1 to 3.9 → 4

−3.1 to −3.9 → −4

Round to nearest

5) round to nearest with ties rounded away from zero

round half away from zero

(integer arithmetic)

  3.5 → 4; 4.5 → 5

−3.5 → −4; −4.5 → −5

6) round to nearest with ties rounded to nearest even

round half to even

(floating-point arithmetic)

  3.5 or 4.5 → 4

−3.5 or −4.5 → −4

Truncation of floating-point data    This is similar to fixed-point. Full details on floating-point will be presented in section 1.6.5.

Rounding    The main rounding options are listed in table 1.1, including several popular designations. The first group, called direct rounding, does not involve ties, and the corresponding designations are self explanatory. For instance, division of integers in VHDL simply discards the remainder, so the result falls in the round down style (examples: 8/3 = 2 and −8/3 = −2).

An important use for rounding is to reduce the error of truncation, for which one of the options in the second group of table 1.1 is normally employed. This group requires a tie-breaking rule. The first option is common for integers, as can be clearly concluded from the examples. The second option, called round to nearest with ties rounded to nearest even, is more interesting; when a tie occurs, it rounds the result to the nearest even value (when rounding to an integer), or, more generally, to the nearest value whose rightmost bit is zero (hence the “round to even” designation). As shown in the examples of table 1.1, 3.5 and 4.5 would both be rounded to 4. Because of this “probabilistic” up/down rounding, the accumulated error due to truncation in the case of successive computations gets reduced. This is the default rounding scheme for floating-point values (IEEE 754 standard), discussed ahead.

The rounding style in line 6 of table 1.1 is detailed in table 1.2, where the four rightmost fraction bits are eliminated. The first line says that if the leftmost of the discarded bits is 0, rounding is not necessary (value is below midway). In the fourth line, the leftmost of the discarded bits is 1 and there is at least another 1 among the discarded bits, so 1 must be added to the remaining vector to round the value up (value is above midway). Finally, in the second and third lines, the discarded vector is 1000, so the value is exactly at midway, requiring round up if the last bit of the remaining vector is 1 (interface is 11) or no rounding otherwise (interface is 01). This procedure causes the last bit of the remaining vector to be always 0 when the original value is at midway.

Table 1.2

Round to nearest with ties rounded to nearest even

Examples of truncations followed by round to nearest with ties rounded to nearest even for unsigned fixed-point data are presented below (the underline character is just to show where the truncation will occur).

10.1_011 (= 2.6875) → 10.1 (= 2.5) Line 1 of table 1.2, value below midway.

10.1_100 (= 2.75) → 10.1 + 0.1 = 11.0 (= 3.0) Line 3 of table 1.2, value at midway.

10.1_101 (= 2.8125) → 10.1 + 0.1 = 11.0 (= 3.0) Line 4 of table 1.2, value above midway.

…

11.0_011 (= 3.1875) → 11.0 (= 3.0) Line 1 of table 1.2, value below midway.

11.0_100 (= 3.25) → 11.0 (= 3.0) Line 2 of table 1.2, value at midway.

11.0_101 (= 3.3125) → 11.0 + 0.1 = 11.1 (= 3.5) Line 4 of table 1.2, value above midway.

Saturation    In this case, the focus is on individual values, so the maximum allowed value is transferred when overflow occurs. For example, the transfer of 110001 (= 49), which is a 6-bit signal, to a 5-bit bus or register, would be 11111 (= 31), which is the largest 5-bit value (assuming the data is unsigned). This is illustrated in figure 1.26; in (a), both addends have N (= 6) bits, but the sum has N + 1 (= 7) bits, so overflow will never occur; in (b), sum too has N (= 6) bits, so when overflow occurs, 111111 is sent out instead of a + b, as indicated symbolically by the multiplexer.

Figure 1.26
(a) Adder without overflow; (b) Adder with overflow and saturation.

1.6.5    Floating-Point Arithmetic

Representation and manipulation rules for floating-point (FP) numbers are defined in the IEEE 754-2008 standard. The material that follows is based on it.

Floating-point representations    The two most popular cases from the IEEE 754 standard are depicted in figure 1.27. The first format is for 32 bits, having one bit for the sign (S), 8 bits for the exponent (E), and 23 bits for the fraction (F). The second format is for 64 bits, with one bit for the sign (S), 11 bits for the exponent (E), and 52 bits for the fraction (F).

Figure 1.27
IEEE 754-2008 standard representations for (a) 32- and (b) 64-bit FP numbers.

There are, however, several constraints in this representation, summarized in table 1.3. Because an actual zero does not exist, the first line shows that a special representation is reserved for it, which consists of filling the whole E and F fields with zeros. Because infinity does not exist either, the representation in the second line is reserved for it and consists of filling the E field with ones and the F field with zeros. The regular (normalized) operation is that in the last line, which shows that the exponent must remain in the 1 ≤ E ≤ MAX − 1 range, where MAX = 2Ewidth − 1, so Emax = MAX − 1 = 2Ewidth − 2, Ewidth being the number of bits in the exponent field.

Table 1.3

IEEE-754 floating-point representation

Sign (S)

Exponent (E)

Fraction (F)

Value

0/1

ZERO (= 000 … 0)

ZERO (= 000 … 0)

+0/−0

0/1

MAX (= 111 … 1)

ZERO (= 000 … 0)

+∞/−∞

0/1

MAX (= 111 … 1)

≠ ZERO

NaN (not a number)

0/1

ZERO (= 000 … 0)

≠ ZERO

Denormalized

0/1

1 to MAX − 1

any

Normalized

MAX = 255 for single precision and 2047 for double precision.

Floating-point to integer conversion    The decimal value (dec) of a FP vector can be determined by the equation below, where the offset factor is BIAS = Emax/2 = 2Ewidth⁻¹ − 1:

dec = (−1)S(1 + F)2E⁻BIAS

For the cases in figure 1.27, the following then result:

32-bit standard: dec = (−1)S(1 + F)2E⁻¹²⁷, where 1 ≤ E ≤ 254

64-bit standard: dec = (−1)S(1 + F)2E⁻¹⁰²³, where 1 ≤ E ≤ 2046

Based on the equation above, the magnitude of the minimum and maximum decimal values (leaving zero and infinity out) that can be represented using the 32-bit standard are decmin = (1 + 0)2¹⁻¹²⁷ = 2⁻¹²⁶ and decmax = (1 + 0.99 …)2²⁵⁴⁻¹²⁷ = (2 − 2⁻Fwidth)2¹²⁷, where Fwidth is the number of bits in the fraction field. Note that the upper limit is very close to 2¹²⁸, which represents infinity.

Two 32-bit examples are presented in figure 1.28, corresponding to the following decimals:

Figure 1.28
32-bit FP representations for (a) 5.0 and (b) −1.5.

(a)  S = 0, E = 129, F = 1/4, BIAS = 127; therefore, dec = (−1)⁰(1 + 1/4)2¹²⁹⁻¹²⁷ = 5

(b)  S = 1, E = 127, F = 1/2, BIAS = 127; therefore, dec = (−1)¹(1 + 1/2)2¹²⁷⁻¹²⁷ = −1.5

Integer to floating-point conversion    The examples below show how to convert an integer into FP representation.

For 0.75: 0.75 = 1/2 + 1/4 = 3/4 = 3 ● 2⁻² = 11₂ ● 2⁻² = 1.1₂ ● 2⁻¹. Therefore, S = 0, F = 1000 … 0, and E = 01111110 (because E − BIAS = −1, so E = 126).

For −2.625: 2.625 = 2+1/2 + 1/8 = 21/8 = 21 ● 2⁻³ = 10101₂ ● 2⁻³ = 1.0101₂ ● 2¹. Therefore, S = 1, F = 01010 … 0, and E = 10000000 (because E − BIAS = 1, so E = 128).

Floating-point extension    Figure 1.29 illustrates the extension principle for FP values, where the exponent field (in white) is extended from 4 to 7 bits and the fraction field (in gray, like the sign bit) is extended from 2 to 4 bits. Note that a₂ down to a₋₂ are transferred directly to b₂ down to b₋₂, and the extra fraction bits are set to zero. The exponent, however, requires some data manipulation, which consists in transferring b₆ = a₃ and b₅ = b₄ = b₃ = a₃′. (Using the expression dec = (−1)S(1 + F)2E⁻BIAS, it can be demonstrated that, for the new and original vectors to have the same value, the relationship E₂ = E₁ + BIAS₂ − BIAS₁ must be satisfied, which is implemented by such equations.)

Figure 1.29
Floating-point extension.

Table 1.3 shows that an exponent filled entirely with ones is either ±∞ or NaN. When that happens in the original vector (detected by the AND gate in the inset of figure 1.29), the output exponent must also be filled with ones (this operation is performed symbolically by the multiplexer). An example of FP extension, following the rules above, is shown below.

a = (1)(0011)(01) → b = (1)(0111011)(0100) (note that a = b = −1.25 ● 2⁻⁴)

Floating-point truncation and rounding    To reduce the size of a FP vector, the opposite of figure 1.29 must occur. The problem is that the relationships b₆ = a₃ and b₅ = b₄ = b₃ = a₃′ (or equivalent, for other vector sizes) must still be satisfied. When it is not, it means that the original value is either too small or too large to fit in the shorter representation. If it is too small, the target vector must be filled with E = 00 … 0 and F = 00 … 0, which represents zero (table 1.3). When it is too large, the vector must be filled with E = 11 … 1 and F = 00 … 0, which represents infinity.

FP reduction consists of fraction truncation, with rounding, followed by exponent reduction using the equations above, if they are respected, or direct transference of zeros or ones otherwise. For example, to reduce a = (0)(0111011)(0100) to b = (0)(b₃b₂b₁b₀)(b₋₁b₋₂), we first truncate and round the fraction, resulting in (0)(0111011)(01) (rounding not needed); because a₅ = a₄ = a₃ = a₆′ is true, we assign b₃ = a₆ and discard a₅a₄a₃, so the final result is (0)(0011)(01). Note that a = (1 + 0.25)2⁵⁹⁻⁶³ and b = (1 + 0.25)2³⁻⁷ are indeed equal.

The IEEE 754 standard defines five rounding options: roundTiesToEven (default; this is the round to nearest with ties rounded to nearest even option, listed in line 6 of table 1.1 and detailed in table 1.2), roundTiesToAway (line 5 of table 1.1), roundTowardPositive (line 1), roundTowardNegative (line 2), and roundTowardZero (line 3).

Detailed examples are shown in table 1.4. The minimum and maximum values of b are bmin = (1 + 0)2Emin⁻BIAS = 2¹⁻⁷ = 2⁻⁶ and bmax = (1 + 3/4)2Emax⁻BIAS = 7/4 ● 2¹⁴⁻⁷ = 7/8 ● 2⁸. Four increasing values for a are listed in the first column; note that the first two are smaller than bmin and the last two are larger than bmax. The first step is to apply fraction truncation plus rounding, resulting the values in the second column (a*). Note that the first value is still below bmin, so b = zero results; however, the second value increases after truncation and rounding, being no longer below bmax, so b = a* is a valid value. The third value got reduced after truncation plus rounding, being no longer above bmax, so b = a* is also a valid value. The final value remained above bmax, so b = infinity.

Table 1.4

Examples of FP truncation and rounding (to nearest with ties to nearest even)

Original value

(a)

After fraction truncation and rounding

(a*)

After exponent reduction

(b)

(0)(0110111)(0100) = 5/8 ● 2⁻⁷

(0)(0110111)(01) = 5/8 ● 2⁻⁷

(0)(0000)(00) = zero

(0)(0111000)(1110) = 15/16 ● 2⁻⁶

(0)(0111001)(00) = 2⁻⁶

(0)(0001)(00) = 2⁻⁶ (min)

(0)(1000110)(1101) = 29/32 ● 2⁸

(0)(1000110)(11) = 7/8 ● 2⁸

(0)(1110)(11) = 7/4 ● 2⁷ (max)

(0)(1000111)(0110) = 11/16 ● 2⁹

(0)(1000111)(10) = 3/4 ● 2⁹

(0)(1111)(00) = infinity

Addition of FP data    We close this section by describing an addition procedure for FP number.

Step 1: If the exponents are different, make them equal by shifting to the right the significand of the number with smaller exponent.

Step 2: Add the significands. However, if a value is negative, enter its two’s complement in the sum; in this case, if the result’s MSB is 1, the sum is negative, so S = 1 and the result must be two’s complemented back to a positive value because in FP notation a negative number is represented with exactly the same bit values as its positive counterpart (except for S).

Step 3: Normalize the result (scientific notation) and check for exponent overflow. If the exponent is outside the allowed range, apply the rules of table 1.3.

Step 4: Truncation and rounding (using the procedure described above) might be required if the fraction is larger than the specified width. Renormalize then the result if necessary.

As an example, consider the addition 0.75 + 2.625, with the result represented with four exponent bits (hence BIAS = 7) and only two fraction bits. As seen already, 0.75 = 1.1₂ ● 2⁻¹ and 2.625 = 1.0101₂ ● 2¹.

Step 1: 1.1 ● 2⁻¹ → 0.011 ● 2¹.

Step 2: 1.0101 + 0.011 = 1.1011; therefore, 0.75 + 2.625 = 1.1011 ● 2¹.

Step 3: The result is already normalized and the exponent, E = 1 + BIAS = 8, is within the allowed range of 1 to 14.

Step 4: The fraction must be truncated to 2 bits. Using table 1.2, we verify that 1 must be added to the last bit of the truncated significand, that is, 1.10 + 0.01 = 1.11. The final result then is 0.75 + 2.625 = 1.11 ● 2¹, from which the following FP representation results: S = 0, E = 1000, and F = 11 (note that this is 3.5, while the exact result is 0.75 + 2.625 = 3.375).

Additional examples are shown below, where a = (0)(11100)(010) = 1.010 ● 2²⁸⁻¹⁵, b₁ = (0)(11101)(001) = 1.001 ● 2²⁹⁻¹⁵, b₂ = (0)(11101)(100) = 1.100 ● 2²⁹⁻¹⁵, and b₃ = (0)(11101)(110) = 1.110 ● 2²⁹⁻¹⁵. The following is obtained from addition operations whose results must keep Ewidth = 5 and Fwidth = 3 (hence with truncation plus roundTiesToEven rounding):

a + b₁ = 1.010 ● 2¹³ + 1.001 ● 2¹⁴ = 1.1100 ● 2¹⁴ → 1.110 ● 2¹⁴ = 1.110 ● 2²⁹⁻¹⁵ = (0)(11101)(110)

a + b₂ = 1.010 ● 2¹³ + 1.100 ● 2¹⁴ = 1.00010 ● 2¹⁵ → 1.000 ● 2¹⁵ = 1.000 ● 2³⁰⁻¹⁵ = (0)(11110)(000)

a + b₃ = 1.010 ● 2¹³ + 1.110 ● 2¹⁴ = 1.00110 ● 2¹⁵ → 1.010 ● 2¹⁵ = 1.010 ● 2³⁰⁻¹⁵ = (0)(11110)(010)

These values will be used in a VHDL implementation in section 10.7 (example 10.6).

2    Review of Sequential Circuits

2.1    Sequential Circuits

This chapter presents a review of sequential circuits and related design principles.

By definition, sequential circuits are those for which the outputs depend not only on the present inputs (which, in fact, might not even exist) but also on previous system states. As mentioned earlier, counters are typical examples of sequential circuits because the next state of a counter depends on the state at which it is now. Except for rare cases (asynchronous-logic based systems, for example), sequential circuits are clocked. By contrast, combinational circuits (reviewed in the previous chapter) are those for which previous states do not matter. A regular adder is an example of combinational circuit because its present computation is not affected by previous computations.

(Before proceeding, see the Note in section 1.1 about VHDL representation and signal names.)

2.2    Latches

Latches and flip-flops, collectively referred to as “registers,” are basic, usually clocked, units capable of storing information. Latches can be divided in two groups, called SR (set-reset) and D (data) latches. The latter, used more frequently, is a level-sensitive memory circuit that is “transparent” while the clock is high and “opaque” (latched) when it is low or vice versa. Corresponding symbols and truth tables are shown in figures 2.1a and b. The circuit has three inputs, called d (data in), clk (clock), and rst (reset), and one output, called q (data out). Sometimes, a second output, called q′, which is just the complement of q, is also present.

Figure 2.1
D-type latches (DLs): (a) Positive-level DL; (b) Negative-level DL; (c) Behavior of a positive-level DL, where the gray shades represent propagation delays.

The DL of figure 2.1a is transparent (that is, q = d) when clk = 1, so it is said to be a positive-level DL (or simply positive DL). The DL in figure 2.1b is transparent when clk = 0 (denoted by the little circle at the clock input), so it is known as negative-level DL (or simply negative DL). When a DL is transparent, its output is a copy of the input, while in the opaque state the output remains in the same state that it was just before the clock changed its condition (represented by q+ = q in the truth tables, where q+ indicates the DL’s next state).

The behavior of a positive DL is illustrated in figure 2.1c, where propagation delays (gray shades) were included to portray a more realistic behavior. Note that reset (asynchronous) has priority over any other signal.

Examples of DL constructions are shown in figure 2.2. That in figure 2.2a is based on a pair of inverters that form a ring. The switch stays closed while clk = 1, causing d to be copied to the ring; when clk = 0 occurs, the switch opens, so the last value of d remains in the ring (this is then a positive-level DL). Note that to reduce contention between d and the value stored in the ring, the feedback inverter is weaker than the forward inverter (i.e., the MOS transistors in the latter have a larger channel width-to-length ratio). A faster DL results with the inclusion of a second switch (no contention), as shown in figure 2.2b; note that when one switch is closed, the other is open. Finally, figure 2.2c shows a detailed implementation (at transistor level) based on figure 2.2b, where the feedback switch is embedded in the inverter (the result is a tri-state buffer, seen earlier in figure 1.1), causing the feedback inverter to be automatically weaker because the transistors are now in series (it also occupies less space than an inverter plus a switch in series). Because flip-flops are constructed with DLs, we will return to this in the next section.

Figure 2.2
Examples of DL constructions: (a) Ring of inverters (SRAM-like); (b) Ring of inverters with additional switch; (c) Detailed implementation based on (b).

2.3    Flip-Flops

Flip-flops can be divided in four groups, called SR (set-reset), D (data), T (toggle), and JK flip-flops. The D-type flip-flop (DFF) is by far the most commonly needed register (indeed, it is the only kind built-in in field-programmable gate arrays, or FPGAs).

Differently from latches, which are level sensitive, flip-flops are edge sensitive. In other words, a DFF is transparent only at one of the clock transitions (either up or down). If the DFF transfers the input value to the output during the clock’s rising transition it is said to be a positive-edge triggered D flip-flop (or simply positive-edge DFF); otherwise, it is said to be a negative-edge triggered D flip-flop (or simply negative-edge DFF). Corresponding symbols and truth tables are presented in figures 2.3a and b.

Figure 2.3
D-type flip-flops (DFFs): (a) Positive-edge DFF; (b) Negative-edge DFF; (c) Behavior of a positive-edge DFF, where the gray shades represent propagation delays.

The behavior of a positive-edge DFF is illustrated in figure 2.3c, where propagation delays (gray shades) were again included. The black dots indicate the values of d that must be copied to q when the clock ticks, but only if reset is not asserted (asynchronous reset has priority over any other signal).

The main time parameters of a DFF are illustrated in figure 2.4a. The input (d) must stop changing at least a time tsetup before the clock transition occurs and must remain so during at least a time thold after the clock transition; therefore, tsetup + thold constitute a forbidden time window as far as data changing is concerned. Another fundamental time parameter is the propagation delay from the moment the clock transition occurs until the output is guaranteed to have settled. This is called clock-to-q propagation delay, represented by tpCQ. Indeed, two values exist for this parameter because the time for q to go from low to high (tpCQLH) can be different from the time to go from high to low (tpCQHL). A similar time parameter is the reset-to-q propagation delay, specified by means of tpRQ (not shown in the figure).

Figure 2.4
(a) Main time parameters of a DFF; (b) Metastability, which can occur when the forbidden time window is violated; (c) Most common synchronizer (also known as two-flop synchronizer).

Metastability    If d changes during the forbidden time interval, q becomes unpredictable and metastability can occur, causing q to take a longer time (possibly more than one clock period) to finally settle to 1 or 0, as illustrated by curves (1) and (2), respectively, in figure 2.4b. The result might depend on which portion of the forbidden time window (i.e., tsetup or thold) was violated, with the output more likely to be resolved in favor of the new value in the former or in favor of the old value in the latter.

When asynchronous data must be stored in flip-flops, there is no guarantee that the forbidden time window will be respected, so the data, or some control signal in the case of multi-bit data (section 2.9), should be synchronized to the local clock before making any use of it. The circuit that does that is called a synchronizer. Its most common implementation is that in figure 2.4c and consists simply of a two-stage single-bit shift register, also known as two-flop synchronizer. Its output (inp_sync) is then a synchronous version of its asynchronous input (inp). Note that this circuit is not guaranteed to capture highs and lows that are shorter than Tclk. Moreover, it adds a latency of two clock cycles (compared to direct storage).

The waveforms in figure 2.4b are for the synchronizer of figure 2.4c. Notice that it is guaranteed to eliminate the effect of metastability when the metastable state gets resolved within a single clock cycle, i.e., when the time-to-resolution is tres ≤ Tclk − tsetup. The probability of metastability can be reduced by reducing fclk (but this is rarely allowed), by employing faster flip-flops (newer technologies), or by using clock gating/sleep techniques, among other options. Fortunately, in well designed DFFs, metastabilty might take years to occur. Finally, notice that the waveforms of figure 2.4b also clarify why a one-flop synchronizer would not be a good solution. Additional details on synchronizers will be seen in section 2.9.

Construction approaches    Figure 2.5 shows construction approaches for DFFs, both based on DLs (D-type latches). The master-slave technique (figure 2.5a) consists of connecting in series two DLs of opposite levels. In the figure, the master (first) is a negative DL, so it is transparent while the clock is low, but then the slave is opaque; when the clock goes up, the slave becomes transparent, copying the master’s output, which in turn becomes opaque. Since that is the only moment at which the input is copied to the output, the result is a positive-edge DFF.

Figure 2.5
Construction approaches for DFFs: (a) Master-slave DFF (employs two cascaded DLs of opposite levels); (b) Short-clock-based DFF (employs a single DL with a clock-narrowing circuit); (c) Example of a commercial implementation of the latter (by Intel).

The short-clock-based technique (figure 2.5b) consists simply of narrowing the clock pulse as much as possible and then using this short pulse (called ϕ) to trigger a DL. Because the transparency window is very narrow, the DL behaves like a DFF. An example of this technique that was used by Intel in the Itanium 2 processor is shown in figure 2.5c. Note that this DL is a slightly simplified version of that in figure 2.2c, with ϕ produced by a conventional AND gate arrangement in which inverters are used to delay one of the gate inputs. In general, the same ϕ is used to trigger several flip-flops.

Two additional features that are often needed in DFFs are illustrated in figure 2.6. The first (figure 2.6a), implemented by adding a multiplexer, is an enable port; when ena = 1, the circuit operates as a regular DFF, while ena = 0 forces the DFF to keep its last value. The second feature (figure 2.6b), implemented with the addition of an AND gate, is a synchronous clear signal; when clear = 1, the DFF’s output is forced to zero at the next (positive) clock edge.

Figure 2.6
DFFs with (a) enable and (b) clear (i.e., synchronous reset) capabilities.

Note: To avoid confusion between asynchronous and synchronous reset, we call the former reset and the latter clear.

Finally, we comment on the T-type flip-flop (TFF), used mainly in counters in ASICs (application specific integrated circuits). A TFF does not involve data; instead, it simply toggles (inverts) its output every time the clock ticks. Two constructions, both derived from a DFF, are depicted in figure 2.7. The first (figure 2.7a) is the most basic, consisting simply of a DFF with an inverted version of q connected back to d. The second (figure 2.7b) has a toggle-enable port; when t = 0, the XOR gate is simply a pass-gate (q is copied back to the DFF), so changes do not occur; however, if t = 1, the XOR acts as an inverter for q, so the circuit behaves like that of figure 2.7a.

Figure 2.7
T-type flip-flops (TFFs): (a) Basic implementation; (b) With toggle-enable capability. A TFF does not involve actual data.

2.4    Glitch Analysis and Prevention

In fully synchronous systems, glitches usually can only occur immediately after clock transitions, which is not a problem in most cases. However, there are cases in which glitches are not acceptable at any time, as when the produced signal must act as clock to another circuit, because the glitches would be understood as clock transitions.

Figure 2.8a shows that glitches can occur in combinational logic even if the incoming signals are synchronous (note that q₀ and q₁ are synchronous versions of a and b). If a and b are different (i.e., 01 or 10), the AND gate (combinational circuit) should produce y = 0; however, because of inevitably different propagation delays (through the DFF and routing), q₁ might reach the AND gate earlier than q₀, causing a glitch in y. The important point here is that when two or more inputs to a combinational circuit change at the same time glitches might occur.

Figure 2.8
(a) Glitchy and (b) glitch-free combinational outputs; (c) Helpful approach (register after combinational logic), which eliminates glitches and aligns the outputs to the system clock (ready also for clock-domain crossing).

A different situation is depicted in figure 2.8b. We know that any signal coming directly from a DFF is glitch free, so q₀ and q₁, as before, are clean signals. However, because now the DFFs are triggered at opposite clock edges, only one of these signals can change at a time, so the output of the AND gate is now glitch free.

A more general case is depicted in figure 2.8c, in which a number of signals, all belonging to the same clock domain, are processed by combinational logic to produce one or more wanted signals. A helpful approach (shown in the figure) is to pass the resulting signals, whose propagation delays are unknown or difficult to predict, through DFFs, which not only remove any glitches but also align the signals to the system clock (preparing them also for clock-domain crossing, for example; discussed in section 2.9). This is not possible however when a signal has transitions at both clock edges (unless dual-edge DFFs are available).

It is also important to emphasize that logic should not be inserted in the clock path because it adds delays (clock skew) and because, more damaging, it might produce glitches. An exception is the case of clock gating (section 2.10), but then specific, well-known strategies are employed to turn the clock on and off. In the case of FPGAs, for example, the clock distribution tree is extremely well constructed, so the less we interfere with it the better.

Finally, remember that glitches must be examined one wire (one bit) at a time. See more on this in appendix D, which presents recommendations on simulation.

2.5    Register Transfer Level of Abstraction

The following definition is presented on page 4 of the IEEE 1076.6 Standard for VHDL Register Transfer Level (RTL) Synthesis: “Register transfer level is a level of description of a digital design in which the clocked behavior of the design is expressly described in terms of data transfers between storage elements in sequential logic, which may be implied, and combinational logic, which may represent any computing or arithmetic-logic-unit logic. RTL modeling allows design hierarchy that represents a structural description of other RTL models.”

In other words, sequential circuits can be represented by basic combinational blocks (modules), like adders, comparators, multiplexers, and so on, followed by storage elements (registers implemented with DFFs), as illustrated in figure 2.9a. A practical example is presented in figures 2.9b and c; in (b), the general architecture of finite state machines (studied in chapter 3) is shown, using a format that highlights the feedback aspect; this architecture is converted to a more obvious RTL representation in (c). Basically all circuit block diagrams shown in this book follow the RTL idea.

Figure 2.9
Illustration of RTL abstraction (transfers between registers via combinational logic): (a) General principle; (b)–(c) A practical example (finite state machine).

When processing a VHDL code, any compiler (Quartus Prime or Vivado, for example) shows an RTL view derived from that code. This can be very helpful for checking the correctness of our design and for making code corrections. Such RTL view will be explored in many of the examples and exercises in the chapters on VHDL.

2.6    Initial Examples of Sequential Circuits

Having reviewed registers, in particular the DFF, we can see now how popular sequential circuits are constructed, all of which involve DFFs. We start with very fundamental ones in this section; others will be shown in section 2.11.

2.6.1    Shift Registers

Shift registers (SRs) are used to store serial or parallel data, converting one form to the other, or to simply delay a data stream.

A serial-in/parallel-out SR is depicted in figure 2.10a. It consists of M (= 4 in this case) stages, directly connected to each other, each formed by N DFFs in parallel (hence each stage is an N-bit register). Its operation is very simple: every time the clock ticks (positive clock edge in this example), the data stored in the registers advance one position. When employed as a delay line, the output of interest is data_out, but when used for serial-to-parallel conversion (i.e., the data is received serially and is made available in parallel), the output is data(0:M − 1). The latter option is useful, for example, at the input of integrated circuits that have too few pins because it allows large data sets (large M) to be entered using only N pins.

Figure 2.10
Shift registers: (a) Regular, operating as a serial-in/parallel-out circuit or as a delay line; (b) With data-loading capability, operating as a parallel-in/serial-out circuit.

A parallel-in/serial-out SR is shown in figure 2.10b. It requires data-load capability, which is achieved by adding a multiplexer at the input of each DFF, controlled by a data-load signal; if load = 1, data(0:M − 1) is stored in the registers (at the next positive clock edge), while load = 0 causes it to operate as a regular shift register (i.e, to move data forward). This SR can then be used for parallel-to-serial conversion, being useful for example at the output of integrated circuits with too few pins.

2.6.2    Synchronous Modulo-2N Counters

Figure 2.11a shows a truly synchronous counter (note that clk is fed directly to all flip-flops), known as synchronous counter with serial enable. It consists of N (= 4 in this case) TFFs interconnected through toggle-enable circuits (just AND gates). Recall that a TFF (figure 2.7) is just a DFF with an inverted version of its output connected back to its input, so it toggles (flips) the output every time a (positive) clock edge occurs, when enabled. If the initial enable port is high (i.e., ena = 1), then t₀ = 1 allows q₀ to toggle at every clock cycle, hence dividing the clock frequency (fclk) by 2; this will cause t₁ to be high during half of the time, so q₁ will toggle every 2 clock periods and hence divide fclk by 4; extending this reasoning to the other stages, they will divide fclk by 8, then by 16, and so on. In summary, q = q₃q₂q₁q₀ is the output of the counter of figure 2.11a, q₀ being its LSB (least significant bit).

Figure 2.11
Synchronous modulo-2N counters using (a) the traditional serial-enable architecture and (b) the incrementer-based architecture.

The reason for calling it modulo-2N is to emphasize that its number of states is a power of two, hence encompassing all 2N N-bit codewords (for example, with 4 bits, it counts from 0 to 15). Consequently, no additional hardware is needed for the counter to return to zero because that will occur automatically and inevitably after the counter goes through all 2N states.

Another implementation approach, based on an N-bit adder (or incrementer) plus an N-bit register, is shown in figure 2.11b. Though usually slightly slower, it might be interesting when adders are readily available (as in FPGAs), while the previous approach (figure 2.11a) is usually preferred in ASICs.

2.6.3    Synchronous Modulo-M Counters

Here, the number of states is M, where M is not a power of two (i.e., 2N⁻¹ < M < 2N). When that happens, additional hardware is needed to zero the counter after the maximum value is reached; for example, to count from 0 to 9, 4 bits (therefore, 4 flip-flops) are needed, which would count from 0 to 15 if no zeroing mechanism were included.

An example is shown in figure 2.12a, which is still a truly synchronous counter but now with a programmable end value p. When q = p, the comparator produces clear = 1, so at the next (positive) clock edge all TFFs are zeroed. Its counterpart, the programmable adder-based implementation, is depicted in figure 2.12b.

Figure 2.12
Programmable synchronous counters of arbitrary modulo using (a) the traditional serial-enable architecture and (b) the incrementer-based architecture.

If the counter of figure 2.12a does not need to be programmable (i.e., the end value, p, is fixed), the comparator is not needed. Instead, some of the AND gates are modified to cause the counter to return to zero (or to any other value) automatically after reaching p.

2.6.4    Asynchronous Counters

Figure 2.13 shows an asynchronous counter. It is called “asynchronous” (a misleading word because indeed things only happen when the clock ticks) because the actual clock only triggers the first stage, each subsequent stage being responsible for clocking the next stage. The skew between the first and the last bit then grows as the size of the counter grows, but this is fine for many applications. As usual, the TFFs (inside dashed boxes) are constructed using DFFs with inverted outputs connected back to their inputs (figure 2.7). The counter’s output is q = … q₃q₂q₁q₀, where q₀ is again the LSB.

Figure 2.13
Asynchronous counter (each stage produces the clock for the next stage).

Besides being the smallest counter in terms of hardware, this approach also saves some power compared to its synchronous counterpart because the clock speed gets reduced to half in each subsequent stage (it is fclk in the first stage, fclk/2 in the second, fclk/4 in the third, etc.).

2.6.5    Gray, Johnson, and One-Hot Counters

This section reviews the sequential, Gray, Johnson, and one-hot codes and also shows how Gray, Johnson, and one-hot counters can be built. These are the main codes used in the encoding of finite state machines (FSMs), as reviewed in chapter 3 and designed in chapters 15 and 16.

Sequential binary code:    This is the regular binary code, whose codewords are ordered according with their corresponding decimal values; for example, 0000 → 0001 → 0010 → 0011 → 0100, etc. With N bits, there are 2N codewords. All counters seen above fall in this category.

Gray code:    The main property of this code is that the Hamming distance (i.e., the number of bits that are different) between neighboring codewords is just one (dH = 1). One way to construct a Gray code is to take a codeword and flip its rightmost bit that produces a new codeword; for example, 0000 → 0001 → 0011 → 0010 → 0110, etc. Similarly to the sequential code, N bits produce 2N codewords.

Johnson code:    This code too has dH = 1 between neighboring codewords, but with N bits, there are only 2N codewords. To build a Johnson code, we simply rotate a codeword, inverting the bit that is moved to the other side; for example, 0000 → 0001 → 0011 → 0111 → 1111 → 1110, etc.

One-hot code:    This code has only one bit different from all the others. Consequently, with N bits, there are only N codewords; for example, 0001 → 0010 → 0100 → 1000. Note that this code exhibits dH = 2 between any two codewords.

The table in figure 2.14a shows all codewords for these four codes for the case of 8 states.

Figure 2.14
(a) Sequential, Gray, Johnson, and one-hot encodings for eight states; (b) Gray counter obtained from a regular counter followed by an XOR layer; (c) Johnson counter; (d) One-hot counter.

A Gray counter can be built in several ways, one of which is depicted in figure 2.14b and consists simply of a regular (sequential) counter (vector b) followed by an XOR layer (vector g). Note, however, that g is subject to glitches because the bits in b do not change exactly at the same time (for example, in the 0111 → 1000 transition of b, all bits change, therefore, depending on the propagation delays, any vector can occur in g momentarily), so g is only statically a true Gray counter. That problem can be solved by registering g (i.e., passing each bit of g through a DFF), resulting in vector greg, which is guaranteed to have a single-bit flip when the clock ticks (this obviously adds a unity offset to the counting, but that is usually not a problem).

A Johnson counter is depicted in figure 2.14c and consists simply of a shift register with its output inverted and connected back to its input.

Finally, a one-hot counter is depicted in figure 2.14d and consists again of a simple shift register but now with the output connected directly back to the input. Note, however, that the reset signal is connected to the preset port of one DFF and to the reset port of all other DFFs, so the circulating sequence has only one bit high.

The popularity of the last two counters above derives from the fact that they are very fast (observe that the only combinational logic in the loop of a Johnson counter is an inverter, while in the one-hot counter there is no combinational logic at all). This, however, led to a misconception in the design of FSMs—a belief that such encodings are always the fastest. If the machine is highly crisscrossed (i.e., there are many interstate connections), the added combinational logic grows quickly, offsetting the speed gain. Of course, if the machine is (nearly) just a big loop, then one-hot might be the fastest option.

2.6.6    Signal Generators

In this section, signal generation means just the generation of arbitrary binary waveforms, which might be required to be glitch free. As shown in figure 2.15a, the circuit receives a reference signal (a clock, in general) and from it produces one or more outputs (a single output, called wave, is shown in this case). An example with just two time slots, of durations 3Tclk (low) and 6Tclk (high), is shown in figure 2.15b, while figure 2.15c shows an example with four time slots, of durations 15Tclk, 5Tclk, 9Tclk, and 40Tclk.

Figure 2.15
Signal generation examples.

These signal generators can be built trivially with just a counter plus one or more comparators. For example, the signal in figure 2.15b can be built with a 0-to-8 counter and two comparators (to 2 and 8) or with a 0-to-5 counter and again two comparators (to 2 and to 5). Obviously, when the number of states is even (12, for example) and 50 percent duty cycle is wanted, a single comparator suffices (just count from 0 to 5, flip the output, and restart from zero).

Several of the circuits described ahead are special cases of signal generators. Classical examples are the clock dividers of section 2.7 and the PWM of section 2.11.4.

2.6.7    Clock Dividers

This is a major topic, so an entire section is dedicated to it (see section 2.7).

2.6.8    Timers and Sequential Binary-to-BCD Converters

These circuits too are part of section 2.7 (see subsection 2.7.4).

2.6.9    Tapped Delay Line

A tapped delay line (figure 2.16) is just a shift register with a programmable number of stages, with the stages being inserted into or removed from the delay line in blocks with power-of-two sizes. Its purpose is to delay data by a programmable number of clock cycles.

Figure 2.16
Tapped delay line.

The circuit inputs are din (signal to be delayed, with N ≥ 1 bits), sel (signal that selects the effective length of the delay line), and the operational signals of clock and reset, while its only output is dout (delayed version of din). As can be seen in figure 2.16, with M blocks, any latency between 0 (for sel = 00 … 0) and 2M − 1 (for sel = 11 … 1) clock periods is possible; for example, if M = 5, the data can be held back by up to 31 clock cycles.

We now move back to more conceptual issues and return to circuit examples in section 2.11.

2.7    Clock Division

Clock division is needed in a number of occasions, like the creation of lower-frequency clocks or the implementation of timers. As will be seen in the next section, to increase the frequency of a digital waveform, a phase locked loop (PLL) or a delay locked loop (DLL) is needed. To reduce it, however, there is an additional alternative, which is the use of a counter. PLLs are capable of producing signals with very low skew and with certain specific duty cycles, but they are expensive and might not be available in the target device or design library. Counters, on the other hand, are inexpensive and can be included in any digital design flow, though the skew is usually larger and they are not able to produce signals with symmetric phase or even constant period in certain cases.

2.7.1    Common Clock Division Cases

Common divisions of fclk are by a power of two, by an arbitrary integer, or by a ratio of arbitrary integers. All three cases are illustrated in figure 2.17, which shows (at the top) the system clock (clk), followed by a counter (count) that is needed to perform the division. The outputs are as follows: clk_a is a division of fclk by 6 (one period of clk_a covers 6 periods of clk), here with symmetric phase; clk_b is a division of fclk by 7, also with symmetric phase (both edges of clk are now used); and clk_c is a division of fclk by 7/3 (7 periods of clk correspond to 3 periods of clk_c), with composition {1/1, 1/1.5, 1/1.5} (each quantity is the number of clock periods). In this case not even constant period is possible, let alone symmetric phase.

Figure 2.17
(a) Clock division by 6, 7, and 7/3; (b) Important features of the resulting signal.

Figure 2.17b points to three aspects that are relevant to our discussion: duty cycle (might be required to be 50 percent), skew (ideally zero, but some skew is not a problem in general), and the existence or not of glitches (unacceptable if a signal is going to play the role of a clock, for example).

To divide fclk by M, M being a power of two, all that is needed is a modulo-2N counter, where M = 2N, N being the number of bits. Its MSB is automatically the desired signal. Either counter seen earlier in figure 2.11 would do. This is therefore the least expensive divider in terms of hardware because a zeroing mechanism is not needed and symmetric phase plus glitch-free output result automatically.

If M is an arbitrary integer, we have to build again an M-state counter, keeping the output low during 50 percent of the time and high during the other 50 percent if symmetric phase is needed. However, since M is no longer a power of two, a counter-zeroing mechanism is now needed. Moreover, if M is odd, the output must exhibit transitions at both (positive and negative) clock edges to obtain 50 percent duty cycle, as can be seen in the clk_b waveform of figure 2.17a. Also, recall that the output might be required to be glitch free. A generic solution for this case will be shown in the next section.

Finally, to divide fclk by M₁/M₂, where M₁ > M₂ are arbitrary nonmultiple positive integers, a specific (as opposed to generic) solution is usually developed as can be anticipated by observing clk_c in figure 2.17a. As additional examples, consider the divisions (using counters) by 33/6 and 35/6; the distributions {2.5/3, 2.5/3, 2.5/3, 2.5/3, 2.5/3, 2.5/3} and {2.5/3, 3/3, 3/3, 2.5/3, 3/3, 3/3}, respectively, are about the best that can be done—the first with constant period but asymmetric phase, the second not even with constant period.

2.7.2    Clock Division by Any Integer with Symmetric Phase

Division of fclk by an even M is straightforward, but it is less obvious when M is odd because the output must then have transitions at both clock edges (see clk_b in figure 2.17a). Probably the most compact generic (i.e., for any integer) solution with symmetric phase and glitch-free output is that in figure 2.18. Waveforms for M = 7 are shown in figure 2.18a. The counter counts from 0 to M − 1 (= 6), and two outputs are produced. These outputs are called clk+ and clk− and are triggered at the positive and negative clock edges, respectively. Both stay low during M/2 (= 3) clock cycles (recall that division of integers discards the remainder) and stay high during the remainder (M + 1)/2 (= 4) clock cycles. The ANDing of these two signals produces the desired output, clk_out.

Figure 2.18
Generic solution for frequency division by any integer with symmetric glitch-free output.

A corresponding hardware is shown in figure 2.18b, where M is the desired divisor (any positive integer). The signal generator (section 2.6.6) operates at the positive clock edge, producing clk+, which is copied by a DFF at the negative clock transition and results in clk−. If M is odd, its LSB—M(0)—is 1, so the ANDed version is sent out; otherwise, clk+ itself is sent out. Because both clk+ and clk− come directly from flip-flops, they are glitch free; moreover, because they cannot change at the same time the AND output is also glitch free. Notice that in any solution for this problem it is not possible to end the circuit with a register as in figure 2.8c (unless dual-edge DFFs are available).

2.7.3    The Cost of Clock Division by an Even Integer

As seen in chapter 1, comparators can be large (and slow) circuits, so avoiding them is desirable. To illustrate that, we analyze here the cost (in terms of hardware) to implement frequency dividers by even integers, which is a common case. An inexpensive approach for dividers by both (even and odd) integers is also presented.

Say that we need to produce a signal with frequency 20 kHz from a 50 MHz clock (so M = 2500). Two options are presented in figures 2.19a and c, having as the only difference the way the counter (which produces signal i) is implemented. In figure 2.19a, a 2500-state counter is used, with the output kept low while 0 ≤ i ≤ 1249 and high while 1250 ≤ i ≤ 2499. Differently, in figure 2.19c, a 1250-state counter is used, with the output kept at one state during one run of the counter and in the opposite state during the next run.

Figure 2.19
(a)–(d) Clock dividers by even integers; (e)–(f) A simple divider by even and odd integers.

An RTL diagram for the approach of figure 2.19a is presented in figure 2.19b. It contains two comparators; once the counter reaches 1249, the upper comparator causes the (symbolic) mux to output a 1, which is then copied by the output DFF at the next (positive) clock edge, producing clk_out = 1. Similarly, when the counter reaches 2499, the lower comparator causes the mux to output a 0, which is copied by the output register at the next clock edge, causing clk_out = 0. In all other counter states, clk_out holds its previous value. Note that the lower comparator serves also to (synchronously) return the counter to zero.

An RTL diagram for the approach of figure 2.19c is sketched in figure 2.19d. Only one comparator is needed now; every time the counter reaches 1249, it enables the toggling of the TFF (reviewed in section 2.3), hence inverting the value of clk_out. This same comparator is responsible for (synchronously) returning the counter to zero.

The obvious difference between figures 2.19b and d is that the latter requires only one comparator. Another, less obvious difference is the size of these comparators. When a comparator is designed to monitor a particular value in a regular binary counter, only the 1s need to be monitored because no previous codeword exhibits the same pattern of 1s. However, in multicomparator processes, only the last comparator can take full advantage of this property, so the extra comparator can be quite large. There is also an additional DFF in the former counter (a minor disadvantage) plus extra combinational logic (represented by the multiplexer).

Another inexpensive approach, this time fine for both (even and odd) dividers, is depicted in figure 2.19e. Its drawback is that the output consists of a pulse that lasts only a single clock period but that is fine in many applications. The corresponding RTL is shown in figure 2.19f, which is similar to figure 2.19d, but the flip-flop is now a D-type. When the maximum value is reached, the output of the comparator goes to one, which is copied by the DFF at the next clock edge (causing clk_out = 1) at the same time that the counter is zeroed. With the counter back at zero, the comparator frees the counter and also causes the return of clk_out to zero at the next clock transition. This is the recommended approach when the divider is (or can be) odd and the application accepts this kind of duty cycle.

2.7.4    Breaking a Large Clock Divider into Smaller Serial Clock Dividers

We close this section by discussing a major decision that must be made when a larger counter (clock divider) has to be broken into smaller serial counters—needed, for example, when BCD (binary-coded decimal) outputs are wanted. This approach prevents the use of a binary-to-BCD converter (section 1.4.5), which is expensive in terms of hardware.

As an example, say that we have to build a timer with BCD outputs that counts and displays seconds from 000 … to 999. … Two solutions are depicted in figure 2.20. In the first (figure 2.20a), each counter produces the clock for the next counter, so it is not a completely synchronous solution and causes clock skew accumulation (though it might not be a problem in the target application). The first counter divides the clock frequency by fclk, producing a clock with a 1-second period (clk_1sec); the second counter divides this clock by ten, producing a clock with a 10-second period (clk_10sec), and so on.

Figure 2.20
Serial counters for a timer with BCD outputs: (a)–(b) Each stage produces the clock for the subsequent stage (hence not completely synchronous); (c)–(d) All stages are triggered by the same clock (completely synchronous). The former has skew accumulation (though this is fine in some applications) but slightly lower power consumption.

An illustrative timing diagram is shown in figure 2.20b (it is very important that the reader examine it carefully). Counter 1 counts from 0 to fclk − 1 (= MAX, called M in the figure), while counters 2 and 3 count from 0 to 9. As indicated in the figure, the resulting clocks are not required to have symmetric phase, and this can simplify their implementation when using dividers that are odd (figure 2.19f versus 2.18b).

The second solution (figure 2.20c) is truly synchronous because all flip-flops are triggered directly by the same clock, so the counters communicate now via enable signals. The counting ranges of the counters are still the same, but note in the timing diagram of figure 2.20d that the enable signals must stay high during only one clock period; otherwise multiple increments would occur. Observe also that ena_1sec is high only while count1 is at its maximum value (M), so count2 is incremented when count1 returns to zero (gray shades were included to help visualize this). In summary, counter 1 is always enabled, counter 2 is enabled only when counter 1 is at its maximum value, counter 3 is enabled when both preceding counters are at their maximum values, and so on. Again, it is very important to understand the time behavior well because this kind of situation will show up in several of the designs developed later with VHDL.

While solution 2 (figure 2.20c) has smaller clock skew, solution 1 (figure 2.20a) saves some power because each subsequent stage is clocked at a lower speed.

2.8    Clock Multiplication and Phase-Locked Loops (PLLs)

To reduce the frequency of a digital waveform (a clock signal, for example), we can use either a PLL circuit or a counter. To increase the frequency, however, only the former is capable (except for multiplication by two—done, for example, with an XOR gate plus delaying inverters, with very poor duty cycle, near either 0 percent or 100 percent). In some applications, a PLL can be replaced with a DLL, which is simpler, particularly for all-digital implementations, but DLLs are slower (up to a few GHz) than PLLs (can go over 100GHz) and are often less versatile and less precise.

FPGAs usually contain several PLLs, allowing the clock frequency to be multiplied (or divided) by a large selection of integers or fractions of integers. As an example of clock multiplication, the DVI (digital visual interface; see section 17.7) video driver, used to connect a desktop computer to its LCD monitor, receives data in parallel and must transmit it to the monitor serially, so the transmission speed must be higher (by ten times) than the internal speed; so depending on the system clock frequency, a PLL is needed. The same is true for HDMI video drivers.

The general PLL principle is summarized in figure 2.21a, where the PLL generates an output whose frequency is fout = M ● fref. The voltage-controlled oscillator (VCO) is an oscillator whose frequency is controlled by a DC voltage. When operating alone, with the VCO voltage around its midpoint, it generates a signal with frequency near the desired value (i.e., M ● fref), but it is neither precise nor stable. This frequency is divided by M in the prescaler, resulting in floop = fout/M, which is consequently near fref. These two signals are compared by the phase-frequency detector (PFD). If floop < fref, the PFD commands the charge pump to increase the voltage applied to the VCO (this voltage is first filtered by the low-pass loop filter in order to attain a stable operation), causing fout to increase and consequently increasing floop as well. On the other hand, if floop > fref, the opposite happens; that is, the PFD commands the charge pump to reduce the voltage sent to the VCO, reducing fout and consequently floop. The process stabilizes (locks) when the output frequency is fout = M ● fref.

Figure 2.21
(a) PLL principle; (b) Internal details of the PFD, charge pump, and loop filter blocks.

Internal details of the PFD, charge pump, and loop filter blocks are presented in figure 2.21b. fref and floop are connected to the clock ports of two DFFs, so the rising edge of fref causes up = 1, while the rising edge of floop causes dn = 1, closing either the upper or lower switch, depending on which signal (up or dn) is asserted first, increasing or decreasing, respectively, the analog voltage stored on the capacitors of the loop filter. After both clocking signals are high, the AND gate resets both flip-flops (after a small time delay), leaving them ready for the process to be repeated at the next positive transitions of fref and floop.

It is important to emphasize that PLLs (and DLLs) cannot be “designed” during a digital design flow (the largest portion of a PLL is indeed analog), so the designer can only count on them if they are built-in in the target device (FPGAs, for example) or if they are available as IP cells in the case of digital ASIC layout.

2.9    Asynchronous Data and Synchronizers

A system might have multiple clock domains and also inputs that are simply unclocked. This section summarizes how to deal with such situations.

2.9.1    Clock-Domain Crossing

As seen in section 2.3 (figure 2.4), when the input to a DFF is asynchronous, it might change around the time the triggering clock edge occurs, causing the output to be unpredictable or even metastable. To reduce this problem (complete elimination is not possible), synchronizers are employed. In what follows, clkA and clkB are assumed to be unrelated.

Four cases are depicted in figure 2.22. The first (figure 2.22a) shows the transference of a single-bit signal sig1 from clock domain A to clock domain B. In this case, the two-flip-flop synchronizer of figure 2.4c (also known as two-flop synchronizer, shown in the gray area) is a classical solution, which is fine also for the unregistered signal sig2. It is important to remember that the two-flop synchronizer introduces a latency of two clock cycles (compared to direct registration) that must be considered in the design of the receiving circuit.

Figure 2.22
Synchronization in clock-domain crossing: (a) Single bit, including also an unclocked case (two-flop synchronizer); (b) Data vector with control signal (mux-based synchronizer); (c) Data vector with request and acknowledge signals (handshake-based synchronizer); (d) Pointer transference (Gray-encoded synchronizer).

The second case (figure 2.22b) shows the transference of an N-bit data signal, which is accompanied by a control signal (data_ready flag). Synchronizing the data (with N two-flop synchronizers) would not help because some of the bits might be corrupted by metastability or by simple convergence to the wrong value while others might not, so the control signal is synchronized instead. In this case, called mux-based synchronizer, the synchronous version of the flag is used to enable, via a multiplexer, the register that stores the incoming data (notice that this multiplexer was seen earlier, in figure 2.6a).

The third case (figure 2.22c) is an advanced, safer version of the previous synchronizer, at the cost of additional hardware and latency. Two flags are employed, both processed by synchronizers, implementing a request and acknowledgement handshake. The transmitter only modifies the data sent to the receiver after the handshake has been completed.

The final case shows the transference of a particular signal: a pointer. Because it is produced by a counter (with N bits), we saw in section 2.6.5 that, if converted to Gray code, only one bit changes from one codeword to the next, so the transference between different clock domains can not corrupt more than one bit (given that the Gray signal is registered). Note that N two-flop synchronizers are now employed. This kind of synchronization is used, for example, in first-in first-out (FIFO) circuits. Another example of application (frequency meter) is shown in the next section.

A final comment regarding FPGAs: Since they usually have several PLLs, high-quality clock generation is easy; however, FPGAs have specific clock trees, so having many internal clock domains might impact their utilization factor. When there are many clock domains, it is recommended to use a clock generator module (available in Xilinx and Intel synthesis tools, for example), which includes clock management and automated PLL instantiation.

2.9.2    A Practical Example: Frequency Meters

This application offers an excellent opportunity to play with synchronization (and RTL concepts). Two solutions (and there are others) are discussed: using Gray code or using a synchronizer to capture pulses that are completely unrelated to our clock.

Frequency meter using Gray code for clock-domain interface    Say that we need to measure the frequency of spikes produced by a physical experiment (represented by x in figure 2.23a), whose frequency can be higher than fclk and whose width and spacing of such pulses can be smaller than Tclk (worst-case scenario), though still within the maximum operating frequency of our circuit.

Figure 2.23
Frequency meter using Gray code to enter the main clock domain.

An option is to use a Gray counter, with the x pulses playing the role of clock directly (we call it clock domain A in figure 2.23a). This counter consists of three parts (figure 2.14b): a regular binary counter, a bin-to-gray converter (XOR layer), and a register. The count (fxa, which is the asynchronous version of fx, the frequency of x) can then be transferred asynchronously to clock domain B because of the important property of Gray counters (seen in section 2.6.5) that only one bit changes at a time, so the registered value can not be off by more than one unit (so metastability is not a major concern). Note in the waveform of figure 2.23b that the write pulse causes the total count in exactly one second to be registered, thus producing a result that is automatically in Hz. Observe also that this circuit stays inactive (i.e., not measuring fx) during only (2Tclk)/(1 second + 2Tclk) of the time; for example, if fclk = 50 MHz, this is ≈0.04 ppm.

The main advantage of this approach is the ease with which the x pulses are captured (there are no major restrictions about the width and spacing of such pulses). On the other hand, the display interface, needed when fx must be exhibited on a display, is more complex to build (section 1.4.5) than in the next approach.

Frequency meter using a synchronizer for clock-domain interface    Another solution is presented in figure 2.24a, where x and clk are again unrelated. The entire circuit is triggered by clk, but note that x is first processed by a pulse conditioner, which converts x to ena, the latter being synchronous to clk.

Figure 2.24
Frequency meter using a synchronizer to enter the main clock domain.

Two pulse conditioners are depicted in figure 2.27. The time diagram of figure 2.24b is for the pulse conditioner of figure 2.27a, which is constructed with a two-flop synchronizer plus a one-shot circuit. It has the limitation that the highs and lows in x must be at least Tclk wide; otherwise they might not be captured. The other pulse conditioner, in figure 2.27b, uses an edge detector plus a synchronizer and is able to capture very short pulses, but the (rising) edges of two consecutive x pulses must be 4Tclk apart for them to be guaranteed to be captured. In summary, this approach has restrictions that the previous one does not have. On the other hand, the construction of the display interface is straightforward here because the counter can be split as in figure 2.20. Finally, note that the write pulse (figure 2.24c) could be used just as an enabler for the register, leaving to clk the actual writing, though this might increase the power consumption slightly because the register would then be clocked continuously at full speed.

2.9.3    Dealing with Reset

The discussion above refers to asynchronous data. We examine now the case of asynchronous reset, for which three cases are depicted in figure 2.25. The simplest case (figure 2.25a) consists of connecting rst directly to the DFF’s reset port so that both the application and the removal of reset are asynchronous. The former being asynchronous is not a problem because it leads the register to a known state (it is advantageous indeed, as will be explained below), but the latter is not so because if rst is lowered when the clock is rising and d is high, the output can be undetermined.

Figure 2.25
Reset alternatives: (a) DFF with conventional (asynchronous) reset; (b) With pseudo-synchronous application and removal; (c) With asynchronous application and truly synchronous removal.

The second case (figure 2.25b) uses an AND gate to turn rst into a locally synchronous command (recall that, for differentiation, we would then call it clear instead of reset); when rst goes to 1, d goes to 0, so the DFF is zeroed at the next (positive) clock transition. However, being rst an asynchronous signal, it might be raised or lowered around the time the clock is rising, so metastability can happen in both cases (application and removal of reset) if d is high, so the behavior is less predictable than in figure 2.25a.

Finally, a semi-synchronous version is depicted in figure 2.25c, in which the application of reset is immediate but its removal is synchronous to the local clock. This is important because reset might be needed, for example, when a critical failure that interrupts the clock occurs; if full synchronization were used, the lack of clock would simply impede the propagation (and hence the occurrence) of the reset signal.

Fortunately, in well-designed flip-flops metastability is a rare event, so it is not uncommon to use the simple option of figure 2.25a. As a final comment, another important issue is careful analysis of the need for reset (and definition of its source, consequently), which is sometimes neglected (see discussion on the importance and sources of reset in sections 3.8 and 3.9).

2.10    Clock Gating

Clock gating consists of stopping the clock during certain time periods in order to save power. A very important feature of a clock gater is not to produce glitches, neither when the clock is blocked nor when it is released. A standard cell might be available for this particular purpose in ASIC layout, but for many applications, particularly at frequencies that are not too high, building it with regular circuitry is fine.

A clock-gating arrangement is shown in figure 2.26a (gray area), where gater is the clock-gating command and gclk is the gated clock. The fundamental point in this circuit is that q must be updated at negative clock transitions in order to guarantee a glitch-free gclk. Consequently, this circuit has indeed three versions: 1) If gater is synchronous but is updated at positive clock edges, then the circuit of figure 2.26a, exactly as shown, is the solution; 2) If gater is synchronous and is updated at negative clock transitions, then the DFF can be eliminated, with gater then connected directly to the AND gate; 3) Finally, if gater is asynchronous, then two DFFs should be used (instead of one) to implement a two-flop synchronizer (as in figure 2.22a but triggered here at the negative clock edge).

Figure 2.26
(a) One of three clock-gating arrangements (check the others in the text) and (b) a corresponding timing diagram.

An illustrative timing diagram is presented in figure 2.26b for version 1 of the circuit (figure 2.26a), where propagation delays were included to portray a more realistic situation. The reader is invited to examine it to confirm that glitches in gclk can not happen. The reader is also invited to draw a similar diagram with gater updated at positive clock edges to confirm that glitches can then indeed occur.

An alternative way to save power is to simply disable the data port of the flip-flops so that most internal transitions are interrupted. However, clock gating saves some additional power because the gates of all flip-flop transistors to which the clock signal is connected are no longer pulsed, thus preventing the charging/discharging of the corresponding parasitic capacitances, which saves power from the clock distribution network.

2.11    Additional Examples of Sequential Circuits

We close this chapter with additional examples of important sequential circuits.

2.11.1    One-Shot and Pulse-Capturer Circuits

Two useful circuits for pulse manipulation in the asynchronous-synchronous frontier are presented here. The first (figure 2.27a), known as one-shot, is used when the length of a pulse (p_in) must be reduced to just one clock period (p_out). The synchronizer is only needed if p_in is asynchronous with respect to our clock.

Figure 2.27
(a) One-shot circuit (reduces pulse length to a single clock period), here preceded by a synchronizer; (b) Pulse capturer (captures pulses that are even shorter than one-half of a clock period), which includes a synchronizer.

The circuit operation is illustrated in the accompanying timing diagram, which the reader is invited to carefully examine. Note that p_out goes up at the second positive clock edge after p_in goes up, producing an output pulse that is synchronous and has duration Tclk. It has a limitation, however, also depicted in the timing diagram: if the distance between two pulses is <Tclk (see space between pulses 2 and 3), that depression might not be detected. In summary, all highs and lows must last at least Tclk for this circuit to be guaranteed to capture them.

The second circuit (figure 2.27b), known as pulse capturer, is used when the pulses are too short and are captured even if they are shorter than Tclk/2 (we assume that p_in is asynchronous). The circuit, constructed with an edge detector plus a synchronizer, gives the pulses a fixed width of one or two clock periods, depending on whether a one-flop or a two-flop (preferred) synchronizer is employed. Note that the synchronizer is not optional here because it is that circuit that resets the edge detector. Details can be seen in the accompanying timing diagram.

This circuit too has a limitation: because the first DFF is reset by the third DFF, the rising edge of a pulse in p_in must be at least three “free” clock periods apart from the rising edge of the previous pulse in p_in for it to be captured (see the time slots marked as 3Tclk in the figure). Note that pulse 2 is three free clock periods after the rising edge of pulse 1, so it is captured, but pulse 3 is only two free clock periods after the rising edge of pulse 2, so it goes unnoticed. Since p_in is asynchronous, three “free” clock periods implies a total of four (generic) clock periods.

2.11.2    Switch Debouncers

Figure 2.28a shows a switch that produces x = 0 when open or x = 1 when closed. The problem with mechanical switches of any type, like toggle switches and pushbuttons, is that they might bounce one or more times before finally settling in the proper position, as illustrated in figure 2.28b. Depending on the switch, such bounces might last up to several milliseconds.

Figure 2.28
(a) A mechanical switch, followed by a debouncer; (b) Signal y is a “clean” version of signal x.

Three approaches for the construction of digital debouncers are presented below. They debounce both transitions (i.e., 0 → 1 and 1 → 0), eliminating unexpected glitches like the noise pulse shown in figure 2.28b.

Approach #1 (for a single switch)    Approach #1 (full-count based) is depicted in figure 2.29a. The first block is just an input register, introduced to eliminate the effect of x and y changing simultaneously (though that is certainly a very rare event, so this block is indeed optional). Note that a synchronizer is not needed because the transference of x to y only occurs after a long time (way longer than any metastable state). The second block is the timer proper, constructed with an XNOR gate, which compares the input to the output, plus a counter, which runs only when y ≠ x_reg (consequently, the counter remains stopped at zero during nearly all the time, which is important for power saving). The final stage is just a single TFF (constructed with a DFF, as seen earlier in figure 2.7), which toggles (flips) its output when the condition y ≠ x_reg lasts long enough such that the counter’s MSB is raised. Note that y, coming directly from a flip-flop, is glitch free. The reader is invited to examine the illustrative timing diagram of figure 2.29b (for N = 2) and check whether it is as expected.

Figure 2.29
(a) Construction approach #1 (full-count based) for fully-featured digital debouncers for single switches; (b) Illustrative timing diagram; (c) A simplified version.

The number of bits (DFFs) in the counter is N = ⎡log₂S⎤, where S = fclk ● Tdeb is the number of states that the counter must span (fclk is the clock frequency, and Tdeb is the debouncing time interval). For example, if fclk = 50 MHz and Tdeb = 20 ms, the counter needs N = 20 DFFs. However, note in figure 2.29 that the counter has N + 1 bits, which is explained below.

A very important feature of this circuit is the way the timer is constructed, which eliminates the need for a comparator (a large circuit). For example, for the numeric values just given, S = fclk ● Tdeb = 10⁶ results, implying a counter running from zero to 10⁶ − 1 = 1111_0100_0010_0011_1111, thus requiring a comparator that is at least 12 bits wide (only the 1s need to be monitored), which is indeed a large circuit. This can be avoided by taking into consideration that the time does not need to be exact in debouncers, so if an extra bit (flip-flop) is included in the counter, its MSB alone can play the role of comparator; when MSB = 1 occurs, it enables the output register, so y is updated, leading subsequently to clear = 1, which zeros the counter (and therefore the MSB too) at the next positive clock edge. In summary, the counter spans a total of S = 2N + 1 states, leading to Tdeb ≈ 2N/fclk = 21 ms for the numeric values given above. For more on this circuit, including clock gating, see exercises 13.12 and 13.14.

A simplified version for the debouncer of figure 2.29a is shown in figure 2.29c (this circuit is treated in exercise 13.13). Notice that, if x comes from a pushbutton and we want y to change (toggle) only when the pushbutton is pressed and not when it is released, one solution is to add another TFF at the output, triggered by y. Another solution is to replace the XNOR gate with an inverter and stop the counter after MSB goes up, clearing the counter when x returns to zero.

Approach #2 (for multiple independent switches)    As seen above, the timer usually consumes a large number of flip-flops, so when there is more than one switch to be debounced, it is highly desirable to find a solution that does not require multiple timers. That is the purpose of approach #2 (slow-clock based), depicted in figure 2.30 for three switches (x₁ to x₃). The counter divides the clock, producing a slow clock (sclk) of period Tsclk ≥ Tdeb, which feeds all three debouncers. To eliminate the need for a (large) comparator, the counter’s modulo is a power of two, so its MSB plays the role of sclk directly. The value of N is ⎡log₂(fclk ● Tdeb)⎤.

Figure 2.30
Construction approach #2 (slow-clock based), for multiswitch debouncers.

The debouncer proper consists of a two-stage shift register that stores the last two values of x; if they are equal, they are copied to the output register. Again, a synchronizer is not needed because the period of sclk is in the milliseconds range. Note that here too the outputs are glitch free.

A drawback of this approach is that the counter stays active all the time and operating at high speed. Because debouncers are needed during only an extremely small fraction of the time, the inclusion of clock gating (section 2.10) would help save power (exercise 13.16).

Approach #3 (for multiple but non-concurrent switches)    When there are multiple switches but they are not supposed to be operated simultaneously, they may share a single timer. A specific example is shown in figure 2.31c. A more general solution can be derived directly from figure 2.29a or c, using one XOR gate per input, with their outputs connected together via a NOR gate, and using also one TFF per output, each enabled by its corresponding XOR gate’s output and having the counter’s MSB as clock (exercise 13.14). Another option is to use the finite state machine approach (exercise 16.6; see also the example in figure 3.32).

Figure 2.31
Reference-value generator: (a) Application example and circuit ports; (b) Fully- and (c) practically-synchronous implementations.

2.11.3    Reference-Value Generators

A circuit that is needed occasionally is one that sets/generates a reference value, like the desired room temperature (adjusted by the user) in an air-conditioning system. In the example of figure 2.31a, ref must be incremented or decremented by one unit every time the up or dn (down) pushbutton is pressed, within the 65°F to 85°F range. The initial value could be, for example, 75°F, set automatically at power up by the reset signal.

Two implementations are presented in figures 2.31b and c. The first is completely synchronous (all blocks are triggered by the system clock, clk). The up and dn signals are debounced and also converted into pulses with just one clock period of duration (by a one-shot circuit, like that in section 2.11.1), so when a pushbutton is pressed, it enables up or down counting (via one of the enable ports, ena_up and ena_dn) during one clock cycle, leading to one-unit increments or decrements.

The second solution (figure 2.31c) is simpler. Since the pushbuttons are not supposed to be pressed at the same time, they share a single-switch debouncer. Note that now the debounced signal operates as clock instead of the system clock, so this circuit is not fully synchronous, but that is not a problem in this kind of application. Observe also that the undebounced up signal is responsible for selecting the counting direction, having plenty of time to settle before the rising edge of the debounced signal (the circuit’s clock) is received.

An interesting question is how to deal with ref if it must be exhibited on a display (exercises 13.29 and 13.30).

2.11.4    Pulse Width Modulator

A digital pulse width modulator (PWM) is depicted in figure 2.32. It takes a corresponding clock (clk_pwm) as input and from it derives a pulse train with variable duty cycle, programmed via the duty input. In the figure, the PWM period (Tpwm) encompasses seven periods of the PWM clock, and the duty cycle is 2/7 (or 28.6 percent) because the output stays high during two out of every seven clock periods. The figure also shows the waveform produced by the internal counter, running from 0 to 6 (thus with seven states), and a table with all PWM values.

Figure 2.32
Digital PWM principle (see implementations in figure 2.33).

The first step in the design of a PWM is the definition of its time resolution (smallest pulse width increment), which determines Tclk_pwm. The second step is to define its intensity resolution (the number of PWM values or shades; equal to 8 in figure 2.32), which determines the number of bits, BITS = ⎡log₂(number of shades)⎤, in duty and the PWM period, Tpwm = (2BITS − 1)Tclk_pwm. The third and final parameter is the division factor (M) to obtain, from the available clock (clk), the PWM clock (clk_pwm), and this is M = fclk/fclk_pwm = fclk ● Tclk_pwm = Tclk_pwm/Tclk (if this is not an integer and the application does not accept approximations, a PLL might solve the problem; it is important to observe that the duty cycle of clk_pwm does not need to be 50 percent). As an example, say that we want Tclk_pwm = 0.5 μs, with a total of 128 PWM intensities, using a clock with fclk = 50 MHz; the resulting parameters are M = 50 MHz ● 0.5 μs = 25, BITS = 7, and Tpwm = 127 ● 0.5 μs = 63.5 μs.

An RTL diagram for the PWM of figure 2.32 is shown in figure 2.33a. The first stage is the clock divider; because the PWM does not have a direct connection to the system clock (it is run by clk_pwm), the implementation of this clock divider is similar to that in figure 2.20a. The second stage is a counter, running continuously from 0 to 2BITS − 2. Next is a comparator, whose output is high while the counter’s output is smaller than duty or is low otherwise. Lastly, comes the output register, which produces a clean pwm signal. Recall, however, that this register introduces a 1 × Tclk_pwm latency, therefore being one clock period late compared to the pwm waveform in figure 2.32 (usually, this is not a problem and can be easily resolved anyway by playing with the comparator).

Figure 2.33
PWM implementations triggered (a) by a local clock (clk_pwm) or (b) directly by the system clock (clk) (see cases in figure 2.20).

Another RTL diagram is shown in figure 2.33b, employing now the approach of figure 2.20c, which is fully synchronous (note that the PWM circuit is now triggered directly by the system clock). The clock divider was replaced with another counter, and the counters communicate now via an enable signal (ena), which lasts 1 × Tclk. Counter 1 is slightly simpler to build than the clock divider of the previous case if one decides to implement clk_pwm with symmetric phase (which is not necessary; the option in figures 2.19e and f would be just fine).

2.11.5    Pseudo-Random Sequence Generators

Linear feedback shift registers (LFSRs) are often used to produce pseudo-random bit sequences. Such circuits, extremely simple, consist of just a shift register with an even number of (predefined) taps connected to XOR gates. As an example, a degree of 8 (due to its 8 DFFs, or, equivalently, N = 8 bits) and 4 taps is depicted in figure 2.34a. Because the desired characteristic polynomial in this example is x⁸ + x⁶ + x⁵ + x⁴ + 1, the taps are taken from the outputs of DFFs 4, 5, 6, and 8 (there are many of such polynomials, even for the same number of bits). This is a maximally length LFSR because it produces (at the eight flip-flops) all possible 2N − 1 = 255 nonzero codewords before the output sequence starts repeating itself (the all-zero state would cause the circuit to lock). The initial state is called the seed; note that q₁q₂ … q₈ = 01010101 was adopted as seed in figure 2.34a (rst goes to the reset input of the odd DFFs and to the preset input of the even DFFs).

Figure 2.34
Pseudo-random sequence generators implemented by linear feedback shift registers (LFSRs): (a) Fibonacci type; (b) Galois type.

The option in figure 2.34a is called a Fibonacci LFSR. An equivalent implementation, called a Galois LFSR, is shown in figure 2.34b. The DFFs are now labeled in the opposite direction and the XOR gates are embedded into the shift register. Its advantage is that the XOR gates now have just two inputs, regardless of the number of taps, so a faster circuit results.

2.11.6    Digital Finite Impulse Response (FIR) Filters

Digital filters are fundamental elements in the digital signal processing field. They are usually classified as finite impulse response (FIR) and infinite impulse response (IIR) filters. The former2 can exhibit linear phase, needed in a number of (telecom) applications, while the latter exhibits sharper frequency response roll-off, thus requiring fewer coefficients, but it can be unstable, so extra care is needed regarding round-off errors, particularly when using integer (fixed-point) values.

General FIR filter architectures    A FIR filter can be represented by the general equation

where x[n] is the current input value, x[n − i] (i = 1 to M) are the previous input values, bi (i = 0 to M) are the filter coefficients, M is the filter order, and y[n] is the current output value. The filter length, which is the number of samples used in the computations, is M + 1. The number of bits used to represent x[n] and bi is N.

Several implementation approaches are depicted in figure 2.35, where a rectangle represents a register (built with DFFs) and a triangle represents a multiplier. The previous values of x are stored in a shift register, with M stages (shown at the top of figure 2.35a, for example; in this case, with N DFFs per stage). The minimum number of bits needed in each branch to avoid overflow are also shown.

Figure 2.35
FIR filter architectures: (a) Direct form, with chain-arranged adders (linear delay); (b) Direct form, with tree-arranged adders (log delay); (c) Transposed form, with pipelined adders (unity delay); (d) Direct form for the particular case of linear phase (coefficients are symmetric).

Figure 2.35a shows the direct form architecture, with the adders arranged in a chain. Because there are M adders in series, the propagation delay is proportional to M, thus linear with respect to the filter order. An improved construction is shown in figure 2.35b, with the adders now arranged in a tree so that the number of adders in series is reduced to L = ⎡log₂(M + 1)⎤, where L is the number of layers in the tree (= 3 here), resulting a logarithmic propagation delay.

The transposition theorem states that the transfer function does not change if we replace all nodes with adders and reverse the direction of all signal paths (i.e., switching input with output and flipping all multipliers). Doing so to the structure of figure 2.35a, the transposed form of figure 2.35c results. Its main feature is that now the adders are automatically pipelined, thus with unity propagation delay, leading to a faster circuit. On the other hand, the size of the registers in figure 2.35c (2N up to 2N + L DFFs per stage) is more than twice the size in figures 2.35a and b (N DFFs per stage). Another difference is that in figure 2.35b the number of bits is optimum; observe, for example, that only the last adder needs the largest number of bits (2N + L), whereas more than one-half of the adders require that size in figure 2.35c (and also in figure 2.35a, for that matter, though this kind of fine optimization is more common in ASIC-based than in FPGA-based designs).

When a FIR filter is designed to operate with linear phase, its coefficients are symmetric, so the number of multipliers can be reduced. This is illustrated in figure 2.35d for the case known as type 1 linear-phase FIR filter, characterized by its even order (note that M = 6) or, equivalently, odd length (note that the number of samples is 7) and by the fact that its central coefficient (b₃ in this example) is different from zero. This configuration has a delay in phase with the clock and is the only one that allows the construction of all four kinds (low-pass, high-pass, band-pass, and band-stop) of filters. The adder array can be the chain type (as in figure 2.35a) or, preferably, the tree type (figure 2.35b).

Number of bits    Since FIR filters might require many adders and multipliers, it is highly desirable to operate with integers (or fixed-point) rather than floating-point values because the latter leads to much larger circuits (usually also slower and with higher power consumption). For an approach that might help in that decision, see the comment entitled “Integer (or fixed-point) versus floating-point” in section 10.7.

In figure 2.35, it was assumed that signed integers are used, with the numbers of bits being shown there for an overflow-free implementation. If the output must be underlimited (to comply with bus size, for example), we can use truncation or saturation (section 1.6.4), but since the relative values of y[n] are usually more important than its absolute values, the former (with rounding included) might be favored. Similar procedures are needed when the internal nodes must also be underlimited (when the filter’s gain is normalized, for example).

Fixed versus programmable coefficients    A final comment regarding how the filter coefficients can be handled: in figure 2.35, fixed coefficients (in the triangles) are employed and hence stored in ROM-like memory. The case of directly programmable coefficients is depicted in figure 2.36 for the direct form architecture. The only fundamental difference is that now the coefficients must be entered (stored) in the circuit prior to any calculations, so an additional shift register is needed to hold bi (i = 0 to M). Even though not shown in figures 2.35 and 2.36, it is obvious that in all cases x[n] and y[n] can be (optionally) registered.

Figure 2.36
FIR filter with programmable coefficients.

2.11.7    Digital Infinite Impulse Response (IIR) Filters

As mentioned, digital filters can be classified as FIR and IIR filters. The former, reviewed in the previous section, can exhibit linear phase, while the latter exhibits sharper frequency response roll-off, so fewer coefficients are needed, but it can be unstable so extra care is needed regarding round-off errors.

IIR filters can be represented by the general equation

where y[n] is the current output value, y[n − i] (i = 1 to M₁) are the previous output values, x[n] is the current input value, x[n − i] (i = 1 to M₂) are the previous input values, ai (i = 1 to M₁) and bi (i = 0 to M₂) are the filter coefficients, and M₂ (≥ M₁) is the filter order. The number of bits used to represent x[n], ai, and bi is N.

Several implementation approaches are depicted in figure 2.37, where again a rectangle represents a register (built with DFFs) and a triangle represents a multiplier; note that M₁ = 2 and M₂ = 3 in these examples. As can be seen, an IIR filter consists of a nonrecursive filter (FIR) in series with a recursive (i.e., with feedback) filter. The structure in figure 2.37a is called direct form I; it consists of a FIR followed by an IIR that are merged through their common adders. The structure in figure 2.37b is the transposed version of that in figure 2.37a and hence called transposed direct form I (review the meaning of the transposition theorem in the previous section). The structure in figure 2.37c is called direct form II; here the IIR precedes the FIR, which are merged through their common registers. Finally, the structure in figure 2.37d is the transposed version of that in figure 2.37c and hence called transposed direct form II.

Figure 2.37
IIR filter architectures: (a) Direct form I (the nonrecursive portion comes first); (b) Transposed version of direct form I; (c) Direct form II (the recursive portion comes first); (d) Transposed version of direct form II.

In figures 2.37b and d, the adders are automatically pipelined, leading to faster circuits. However, the architecture in figure 2.37b (also known as canonical form), besides being fast, requires fewer registers and adders.

There are also other structures for IIR filters. One of the main cases, which leads to a modular implementation with a sensitivity to round off errors lower than that of the structures of figure 2.37, is a building block called BiQuad, derived from the structures of figure 2.37, with M₁ = M₂ = 2 and some modifications in the multiplier values. BiQuad blocks are then associated in series or in parallel to produce the final IIR filter. A drawback of such approach is its reduced speed compared to the pipelined structures (figures 2.37b and d).

2.11.8    Serializer and Deserializer Circuits

Serializer and deserializer circuits convert parallel data into serial data and vice versa. They are needed in a number of applications, like serial data communication, as illustrated in figure 2.38. For instance, the DVI and HDMI protocols (section 17.7 and appendices J–K) are examples where serializers and deserializers are needed.

Figure 2.38
Data serializer and deserializer circuits employed in serial data communication.

Serializers can be implemented in several ways. A shift-register-based approach is depicted in figure 2.39a. The first block is a PLL (section 2.8), which multiplies fclk by M (= 4 in this example), producing clk_x4. The PLL is needed because we are assuming that the parallel (slow) side is being processed at the speed of our system clock (clk). The second block produces an enable signal (load) for loading data into the shift register once every four periods of clk_x4. The third part is just a shift register with data-load capability (figure 2.10b). A corresponding timing diagram is presented in figure 2.39b, which the reader is invited to examine.

Figure 2.39
(a) Shift-register-based data serializer and (b) a corresponding timing diagram; (c) Multiplexer-based data serializer.

Another approach for serializers is the multiplexer-based construction of figure 2.39c (PLL not shown). First, the multi-bit input (din) is stored locally using the system clock (clk). Next, each of the stored bits feeds one input of an M-input multiplexer whose select port (sel) runs from 0 to M − 1 using the high-speed clock (clk_x4), hence sending out data one bit at a time. Obviously, sel must start incrementing right after din is updated, so some sort of synchronization involving clk and clk_x4 must be provided (not shown in the figure). This approach is used in the serializers for the DVI video driver designed in section 17.7 using VHDL.

3    Review of Finite State Machines

3.1    Finite State Machines

This chapter reviews fundamental concepts and design principles related to finite state machines (FSMs), based mostly on [4].

As we know, digital circuits can be classified as combinational or sequential. FSM is a modeling/design technique for the latter. At any time, the machine sits in one of a finite number of possible states. For each state, both the output values and the transition conditions to other states are known. The transition conditions are reevaluated at every clock transition (positive edge, by default), so the state-change procedure is always synchronous because the machine can only move to another state when the clock ticks. The output, on the other hand, can be synchronous (Moore machines, default) or asynchronous (Mealy machines).

The FSM approach has important benefits:

1)  It is a systematic approach (a method) for designing sequential circuits.

2)  The resulting solution is at least near optimal (for the given encoding).

3)  A priori knowledge about how the general circuit (solution) should look like is not required.

4)  Also, it allows the encoding style to be chosen, has an easy inclusion of reset, and can be lock free (“safe” state machine construction).

This technique, however, is not recommended for any sequential circuit. For example, a counter might have many states, so enumerating them all would be unfeasible (and there are well-established approaches for designing counters anyway). On the other hand, all sorts of controllers are potential candidates for the FSM approach, especially if they involve a large number of if conditions (i.e., if the machine states are highly interconnected). It is important to stress that implementing FSMs in hardware is substantially more complex than doing it in software.

(Before proceeding, see the Note in section 1.1 about VHDL representation and signal names.)

3.2    State Transition Diagram and Machine Types

The first (and most important) step when designing an FSM is to correctly draw its state transition diagram, which is a graphical representation of system’s functional specifications. Such a diagram must obey three fundamental principles:

1)  It must include all possible system states.

2)  All state transition conditions must be specified (unless a transition is unconditional) and they must be truly complementary.

3)  The list of outputs must be exactly the same in all states.

There are two types of FSMs, called Moore and Mealy. Both are described next using corresponding state transition diagrams.

Moore-type state machines    An FSM is said to be of Moore type when its output depends solely on the machine’s present state. In other words, the output is not affected directly by the input (the input can only affect the machine’s next state). The result is a fully synchronous circuit because the state and the output can only change when the clock ticks.

An example of Moore machine is presented figure 3.1. The circuit ports are shown in figure 3.1a, with input x (a serial bit stream), plus the operational signals of clock and reset, and output y (a single-bit flag). As depicted in figure 3.1b, the circuit must detect the sequence 010 in x, producing y = 1 when that sequence occurs, but note that overlaps are not allowed.

Figure 3.1
FSM that detects the sequence 010, without overlaps: (a) Circuit ports; (b) Problem description; (c) Corresponding Moore state transition diagram.

The corresponding Moore state transition diagram is shown in figure 3.1c, containing four states (called one, two, …), one input (x), and one output (y). The input is always expressed along the arrows (because it controls the transitions), while the output is always expressed inside the state circles (because in a Moore machine the output in each state is unique). In this example, the output is raised only when state three is reached.

Another example is presented in figure 3.2. The circuit ports are shown in figure 3.2a, where data consists of serial characters from an extended ASCII table and flag is an indicator that the sequence “abc” has been detected (that is, that data = 01100001, followed by data = 01100010, and then data = 01100011 were received). The corresponding Moore diagram is shown in figure 3.2b. Note that a simpler notation was employed now for the transition conditions (a means x = a, while !a means x ≠ a or, equivalently, “x is not a”; also, & means and, while | would mean or).

Figure 3.2
FSM that detects the ASCII sequence “abc”: (a) Circuit ports; Corresponding (b) Moore and (c) Mealy state transition diagrams.

Mealy-type state machines    An FSM is said to be of Mealy type when its input can affect the output directly. In other words, the output now does not depend solely on the machine’s state but also on the input. The resulting circuit is no longer truly synchronous because the output might now change independently from the clock.

A Mealy-type solution for the problem of figure 3.2a is depicted in figure 3.2c. Since the output can now exhibit more than one value for the same state, the output values are no longer expressed inside the state circles; instead, they are expressed along the arrows, together with the input (transition condition) values. Additionally, to simplify the notation, in a Mealy machine the signal names are generally omitted. In this example, the Mealy parameters are “data; flag”—meaning “if data = <value>, then flag = <value>”; for example, “a; 0” means “if data = a, then flag = 0.” The or function, which occurs in the C to A transition, is represented by a vertical bar (|). When there are two or more output values, commas are used to separate them.

The meaning of the state diagram of figure 3.2c is as follows. If the circuit is in state A and the input is data = a, the output is flag = 0, and the next state (at the next positive clock edge) will be B; otherwise, the output is still 0, but the machine will remain in state A. Likewise, if the machine is in state C and the input is a, then the output is 0, and the next state will be B; otherwise, if the input is c, the output is 1, and the next state will be A; else, the output is 0, but the next state will still be A. The direct dependence of the output on the input can be easily observed in the state diagram; for example, in state C, the output flips between 0 and 1 independently from the clock if the input flips between !c and c, respectively.

3.3    Representing versus Implementing

Figure 3.3 shows weak and strong candidates for implementation using the FSM approach. The more inputs and crisscrossed interconnections the diagram has, the better the candidate, especially when involving time (t).

Figure 3.3
Examples of weak and strong candidates for FSM-based implementation.

There is, however, an important difference between representing a problem as an FSM (by means of its state transition diagram) and implementing it using the FSM approach (which is very formal—section 3.12). The diagram has the property of putting the functional specifications together, exposing the problem clearly and hence helping the designer decide which implementation approach to pursue. It helps also to document and debug the design, regardless of the actual implementation approach. In summary, do not be afraid to draw a state diagram whenever your problem involves a number of if conditions, even if you are not implementing it subsequently using the FSM approach.

3.4    Moore-to-Mealy and Mealy-to-Moore Conversion

A Moore machine can be easily converted to a corresponding Mealy machine and vice versa. The latter can have fewer (never more) states than the former, though the difference is usually (negligibly) small in actual engineering problems.

The conversion procedure from Moore to Mealy is illustrated in figures 3.4a to c. The original Moore machine is shown in figure 3.4a. The first step (figure 3.4b) is to remove the output values from inside the state circles and associate them to the inward transitions. The next step is to examine if state merging will be possible after the conversion, which is the case when two states have equal outward transitions. Note that in figure 3.4b the transition governed by x₁;yC in states A and B goes to C and that governed by x₂;yD goes to D, so A and B can be merged. The final step is to merge those states, connecting to the resulting state all inward transitions of the merged states. This leads to the final FSM of figure 3.4c.

Figure 3.4
Examples of (a)–(c) Moore-to-Mealy and (d)–(f) Mealy-to-Moore FSM conversions.

The visual inspection above covers only first-order merging, but that is usually sufficient in the simplification of practical FSMs because of the incremental nature in which actual state transition diagrams are constructed. It is also important to mention that, even though Mealy might save flip-flops (due to fewer states), it is not a major issue because, to be guaranteed to save just two D-type flip-flops (DFFs), more than 50 percent of the states must be eliminated (assuming minimal-bit encoding), which essentially does not occur in actual designs.

The Mealy-to-Moore conversion is illustrated in figures 3.4d to f. The original Mealy machine is shown in figure 3.4d. The first step (figure 3.4e) is to move the output values from the inward arrows to the interior of the state circles. The next step is state decomposition (the opposite of state merging) because in a Moore machine the output value in each state has to be unique. States A and B, having two different output values, must be decomposed into two states, so A and B give origin to A′–A″ and B′–B″, respectively. Because A has a self-transition (i.e., a transition to itself) governed by x = x₁, that transition must now occur in A′ itself and also in A″ to A′, with the same occurring in B′ and B″, which leads to the final Moore machine of figure 3.4f.

3.5    Time Behavior of Moore versus Mealy Machines

It is indispensable to understand well how Moore and Mealy machines behave in the time domain because that affects not only the choice between one and the other but also the way they are implemented.

The general time behavior is illustrated in figure 3.5. A Moore machine, with states A, B, and C, is shown in figure 3.5a. Assume that the input and output are called x and y, respectively. So when in state A, the output is y = a, and the machine moves to state B at the next positive clock transition if x = 1 or remains in state A if x = 0; the behavior in the other states is analogous. A corresponding timing diagram is presented in figure 3.5b, showing that both the state and the output (y) only change when the clock ticks. If an (optional) output register is employed (the reasons for using it are listed in the Note of section 3.12), then y_reg is produced, which has a latency of one clock period (or one-half if the other clock edge is employed) with respect to y. It is important that the reader analyze and thoroughly understand this time diagram.

Figure 3.5
(a) A Moore machine and (b) an illustrative timing diagram; (c) Corresponding Mealy machine, without simplification; (d) After simplification (states A and C are merged, forming X); (e) Timing diagram for the FSM in (c).

A corresponding Mealy machine is shown in figure 3.5c, still in the first stage of conversion from the Moore format. Even though it has not undergone possible simplifications (state merging), it is a perfectly valid Mealy machine. Its timing diagram is presented in figure 3.5e, where its asynchronous behavior can be clearly observed; note that the output does not change only when the clock ticks (only the state is still truly synchronous). If the (optional) output register is included, y_reg results, which, contrary to y, is obviously synchronous.

The simplified (final) Mealy machine is shown in figure 3.5d. Recall, however, that the hardware reduction in actual designs is normally negligible, so implementing that in figure 3.5c instead of that in figure 3.5d is usually just fine.

Major conclusions can be drawn by comparing figures 3.5b and e:

1)  In the Moore machine, both the state and the output only change when the clock ticks, whereas in the Mealy machine that is true only for the state (the output can be asynchronous);

2)  The sequence of states is the same in both cases;

3)  Finally, if both machines have their output registered, the output of the Mealy machine (which then is synchronous too) becomes equal to the unregistered output of the Moore machine (compare y_reg of figure 3.5e to y of figure 3.5b). In summary, the extra latency of y_reg in the Moore case can be avoided by using y_reg of Mealy.

Another example, called a one-shot circuit (seen earlier in section 2.11.1), is shown in figure 3.6. It converts the duration of a (longer) pulse to a single clock period. Two solutions are presented, using a Moore (figure 3.6a) or a Mealy (figure 3.6b) machine (the latter resulted after the conversion procedure described in section 3.4 was applied to the former).

Figure 3.6
A one-shot circuit implemented using (a) Moore and (b) Mealy machines; (c) Corresponding timing diagram.

A corresponding timing diagram is presented in figure 3.6c, where gray shades representing propagation delays were included to portray a more realistic situation. It is assumed that everything (including the incoming signal) is triggered/updated at positive clock edges. As in the previous example, the sequence of states is the same in both machines, but the outputs are different. Both produce a correct result, but note that ymoore is one clock period behind ymealy; if that is a problem in the target application, the Mealy solution should be employed.

3.6    Choosing between Moore and Mealy Machines

Having understood the time behavior of Moore and Mealy machines, the choice between one and the other should be relatively simple. Such properties and conclusions are summarized below.

1)  Moore machines are fully synchronous (both the state and the output only change when the clock ticks), whereas in Mealy machines only the state is necessarily synchronous (the output might change as soon as the input changes—check signals x and y in figures 3.5b and e).

2)  Therefore, Moore machines are usually preferred because most digital systems are fully synchronous. Moreover, Moore machines are simpler to design and implement.

3)  However, sometimes immediate action is required at the output when the input changes, in which case Mealy is the natural choice (see signals x, ymoore, and ymealy in figure 3.6c).

4)  Additionally, output-registered Mealy machines are a great substitute for output-registered Moore machines when the latency introduced by the output register in the latter is not acceptable in the target application. And, because that register causes the Mealy construction (as a whole) to become completely synchronous, system synchronicity is preserved.

5)  Mixed Moore-Mealy machines are needed occasionally. That occurs when only some of the outputs must be registered, so the relative phase of all outputs is preserved (and latency is avoided).

6)  A practical consideration: Even if the target implementation is Mealy, always start by drawing a Moore state diagram (simpler and safer to draw), then convert it to Mealy (as in figure 3.4). Interestingly, the simplifications (state merging) are often not necessary because the reduction in hardware (if any) in actual engineering problems is usually negligible; in other words, using only the first step (from figure 3.4d to 3.4e) in the conversion procedure is often sufficient.

3.7    Transition Types

A very important classification for the transitions is reviewed here that, as will be seen later, immensely simplifies the hardware-based design of FSMs.

All four transition types are depicted in figure 3.7, where x is the actual input, t is an auxiliary input generated by a timer, and y is the actual output. They operate as follows.

Figure 3.7
State machine containing all four possible transition types.

Transition AB (conditional transition):    Depends only on the actual input, x. If the machine is in state A, it must move to state B at the first (positive) clock edge that finds x = x₁.

Transition BC (timed transition):    Depends only on the time, t. The machine must stay in state B during exactly T₁ clock cycles, moving then to state C. An auxiliary circuit (a timer, which is simply a counter, counting from 0 to T₁ − 1 in this state, thus spanning T₁ clock periods) must be included in the design. By default, the timer is zeroed every time the FSM changes state; moreover, the timer is kept stopped at zero in the states where it is not needed (states A and D in figure 3.7).

Transition CD (conditional-timed transition):    This transition is more complex because it depends on the actual input, x, and also on the time, t. The machine must move to state D at the first (positive) clock edge that finds x = x₂ after staying in state C for T₂ clock cycles. This implies that it will remain in state C during at least (instead of exactly) T₂ clock cycles.

Transition DA (unconditional transition):    This is the simplest type of transition. The machine must move from state D to state A at the next (positive) clock edge, regardless of x and t, thus staying in D during exactly one clock period.

Note in the description above that even though t denotes time, it is not expressed in seconds, but rather in “number of clock cycles.” For example, if we want the machine to stay in a certain state during tstate = 2ms, and the clock frequency is fclk = 50MHz, we simply adopt Tstate = tstate × fclk = 2 ● 10–3 × 50 ● 10⁶ = 100,000 clock cycles.

3.8    Incorrect State Transition Diagrams

We describe next some popular mistakes made while developing a state transition diagram.

Under-specified states    An FSM is said to be underspecified when one or more states do not include all possible transition conditions. An example is shown in figure 3.8a, where in both states the condition “a = 1 & b = 0” is not covered. The corrections obviously depend on the problem specifications; in figure 3.8b, state A was fixed by adding the missing condition to the self-transition AA, while in state B it was fixed by removing the dependence on a. A different correction, by means of implicit “else,” is shown in figure 3.8c, which means that under any condition other than the listed ones the machine must remain in the state that it is.

Figure 3.8
Examples of (a)–(c) under- and (d)–(e) overspecified FSMs.

Overspecified states    An FSM is said to be overspecified when the same condition satisfies more than one outward transition. An example is presented in figure 3.8d; note that if “a = 1 & b = 1” occurs, both AB and AC transitions are allowed. This is fixed by setting priorities; in figure 3.8e, priority was given to AB.

Incomplete output list    As will be seen shortly, the outputs of an FSM are produced by its combinational logic and are therefore without memory (unless by coincidence the combinational logic is just a wire, so the output is taken directly from a DFF in the state register). Consequently, when the machine changes its state, it does not “remember” what the output values were before. This means that we must declare all output values in all states. In VHDL, forgetting to do that leads the compiler to infer latches, impairing the system speed and reliability.

State bypass    The state-bypass problem occurs when a transition condition for entering a state coincides with a transition condition for leaving that state, and that condition lasts multiple clock periods. As an example, consider the car alarm of figure 3.9a. If the machine is in the disarmed state and a command from the remote control (rem = 1) is received, it moves to the armed state, ready to detect any intrusions; however, because rem = 1 might last several clock cycles, the circuit goes back to disarmed, returning then to armed, and so on, producing a kind of state bypass (in fact, the states are not exactly bypassed, but rather the machine remains in each state for just one clock period instead of holding to a specific state). Note that this occurs also when a sensor detects intrusion (sen = 1). This problem can be solved with some kind of flag or, more clearly, with wait states (recommended), as in figure 3.9b (white circles), where states wait1 and wait2 wait until rem returns to zero before allowing any other action to take place. (Note that the resulting FSM, in figure 3.9b, is overspecified; the reader is invited to locate the problem and fix it—check then figure 3.26.)

Figure 3.9
Car alarm (a) with and (b) without the state-bypass problem (but note overspecification).

Incorrect timer construction    When there are time-dependent transitions (as in figure 3.7), it is important not to mix the timer (which is just a counter, hence with well-defined, very simple implementation techniques) with the main machine. Instead, the timer should be built separately, with its output serving as an auxiliary input to our machine; however (and this is the crucial part), it is our machine that must control the timer, deciding when it should run or stop and when it should be zeroed. This concept will be used extensively later in our discussion on timed FSMs.

Lack of reset    Unlike FPGAs, in which flip-flops are normally reset automatically at power up, the initial state of flip-flops is usually arbitrary in ASICs, so if no reset mechanism is provided to place the machine in a specific initial state, its initial location will be arbitrary. One of two situations will then result: either the initial state is internal (that is, belongs) to the machine or it is external to it. An example is shown in figure 3.10a, where three bits were employed to encode a five-state FSM (internal states A1–A5), so there are three unused codewords (external states B1–B3); if the initial state is B1 or B2, convergence will eventually occur (to state A1 or A3, respectively), but if it is B3, the machine will get locked.

Figure 3.10
The reset issue in FSMs: (a) FSM with external states, being B1 and B2 convergent and B3 nonconvergent; (b) Resetless FSM encoded using one-hot or Gray code; (c) Typical sources for the reset signal.

Another example is shown in figure 3.10b, where a four-state counter is encoded first using one-hot code (the corresponding pr_state = q₃q₂q₁q₀ codewords are listed below the state circles). The (optimal) equations for nx_state = d₃d₂d₁d₀ are d₃ = q₂, d₂ = q₁, d₁ = q₀, and d₀ = q₃, so if the initial state is q₃q₂q₁q₀ = 0000, for example, d₃d₂d₁d₀ = 0000 results, causing the machine to lock (because nx_state = pr_state). If the same counter is encoded using Gray code (also listed below the state circles), all possible (2-bit) codewords are used, so the initial state can only be internal to the FSM, resulting a deadlock-free circuit.

In summary, only after carefully studying the problem specifications and the state transition diagram one can decide not to use a reset signal. If reset is needed, then its source must be determined, which usually falls in one of the three cases shown in figure 3.10c. Finally, if the application is critical, the “safe state machine” option (described next) should be used.

3.9    Safe State Machines

Illegal states can occur due to noise or to asynchronous inputs (either clock-unrelated or from another clock domain). The inclusion of synchronizers (section 2.9) in the latter is obviously highly recommended.

Synthesis tools allow FSMs to be implemented in “safe” mode (details are given in section 9.5.2). The tool adds extra logic to the circuit such that states that do not belong to the machine (like B1 to B3 in figure 3.10a) are detected and are followed automatically by a corrective action, like a reset.

Incorrect states can be detected in several ways, like the use of comparators for sequential encoding or of simple error correcting codes, like Hamming-2 for detecting an error (and then resetting the machine) or Hamming-3 for fixing a single-bit error. On the down side, the amount of logic added to the design can be substantial.

3.10    Fundamental Hardware Architectures for FSMs

As we have seen, the state transition diagram is a representation for the system’s functional specifications. We turn now to another representation, which is for the machine’s hardware.

A general view is presented in figure 3.11a, divided in two blocks: a lower block containing only the state register (a set of flip-flops) and an upper block containing all the combinational logic. The state presently stored in the register is called pr_state, while that to be stored at the next (positive) clock transition is called nx_state. The upper section has then two inputs, called inp (actual input) and pr_state, and two outputs, called outp (actual output) and nx_state.

Figure 3.11
Fundamental FSM architectures from a hardware perspective.

The upper block is shown broken in two portions in figures 3.11b and c. In the first, the input goes only to the portion that computes the next state, so the output is not affected directly by the input; consequently, the output (and the state, of course) can only change when the clock ticks, resulting a fully synchronous circuit (a Moore machine). In figure 3.11c, the input is fed to both portions of the upper block, so now the output can be affected directly by the input, resulting a circuit that is not fully synchronous (a Mealy machine).

Figure 3.11d shows that mixed Moore-Mealy machines can also be constructed. Notice that outp1 is affected only by pr_state, so this is a Moore-type output, while outp2 is affected by pr_state and inp, resulting a Mealy-type output. As already mentioned, mixed machines are useful when only some of the outputs must be registered, so the relative phase of all outputs is preserved (and latency is avoided).

A final important aspect to observe in figure 3.11 is that the output is always produced by the combinational logic block, so the machine has no memory about it, and that is why we need to specify all output values in all states. Other hardware parts will be added later to these fundamental architectures.

3.11    Encoding Styles

It is up to the designer to select the encoding scheme for the machine states. The most popular options are sequential, one-hot, and Gray encoding, all seen already in section 2.6.5, including their pros and cons. Details on how to use these styles in VHDL are given in section 9.5.1.

3.12    Fundamental Design Technique for FSMs

This section reviews a fundamental design technique for Moore machines (its extension to Mealy machines is straightforward). It is a four-step design procedure that results in the structure of figure 3.11b. An additional step, which is external to the machine proper, is also described.

Step 1:    Draw the state transition diagram. (Use always the Moore style, converting it afterward to Mealy when that is the target implementation.)

Step 2:    Based on the state diagram, build two truth tables, one for the next state, the other for the output. Then rearrange the truth tables, replacing the state names with signal names (q for flip-flop outputs, d for flip-flop inputs) and using corresponding binary values (to do so, the encoding scheme must be defined).

Step 3:    Extract from the rearranged truth tables the optimal boolean equatons for nx_state and for the output.

Step 4:    Draw the corresponding circuit, placing all flip-flops (DFFs only) in the lower section and all combinational logic for the equations derived above in the upper section.

Step 5:    Analyze the application and include an extra register at the output (not shown in figure 3.11) if necessary (see Note below).

Note:    The output register is not part of the FSM itself. The main reasons for adding it are:

1)  To eliminate occasional glitches. Recall, however, that glitches can only happen in some cases (it depends on the combinational logic that produces the output) and only immediately after triggering clock edges in Moore machines, so they are not a problem in most applications.

2)  To produce a pipelined/RTL construction as in figure 2.8c with a more predictable time behavior (less skew, possibly higher maximum operating frequency).

3)  To simply delay a signal or follow a synchronous design style.

The design procedure described above is illustrated next using the FSM of figure 3.1, repeated in figure 3.12a for convenience.

Figure 3.12
Design of a nonoverlapping “010” detector: (a) State transition diagram; (b)–(c) Truth tables for next state and output; (d) Corresponding Karnaugh maps and minimal boolean equations; (e) Resulting circuit.

Step 1: The corresponding Moore diagram is that of figure 3.12a.

Step 2: The truth tables for nx_state and y are in figures 3.12b and c. The tables were written first using the state names, based directly on the state transition diagram, and then rearranged using signal names (q, d) and binary values. Note that sequential encoding (section 2.6.5) was chosen.

Step 3: From the truth tables, the Karnaugh maps of figure 3.12d result, from which the equations for d₁, d₀, and y are obtained.

Step 4: Based on these equations, the circuit is drawn (figure 3.12e).

Step 5: Say that in this application glitches are not acceptable. Then we must analyze the circuit to check whether the implementation is subject to glitches. In the present example, we saw that y = q₁ ● q₀. Moreover, we know that q₁ and q₀ can change at the same time (see in figure 3.12c that the transition between states one and two requires both to change, but the output must remain low in both cases; depending on the routing inside the chip, q₁ might go to 1 before q₀ comes down to 0, so the AND gate will produce a momentary 1—a glitch). An output DFF can then be added to render a glitch-free y. (Recall that this DFF introduces a latency of one or one-half of a clock period, depending on which clock edge triggers it; if that extra latency is a problem, a Mealy machine should be used, as explained in sections 3.5 and 3.6.)

Simulation results from the circuit of figure 3.12e, synthesized with VHDL, are shown in figure 3.13. Note the following (expected) results: the output only changes at (positive) clock edges (Moore machines are synchronous); the output goes to 1 at the first (positive) clock edge after the sequence 010 occurs; overlaps do not cause the output to go to 1; and glitches do occur at the output.

Figure 3.13
Simulation results from the circuit of figure 3.12e, synthesized using VHDL.

3.13    State Machine Categories

In [4], a new classification for FSMs was introduced, which places any FSM in one of three categories called regular, timed, or recursive, immensely easing their hardware-based design. As a result, the first two decisions that a designer must make are:

1)  The machine category (regular, timed, or recursive).

2)  The machine type (Moore, Mealy, or mixed).

All three categories are illustrated in figure 3.14.

Figure 3.14
FSM categories from a hardware perspective.

Category 1 (regular) FSM:    Contains only conditional transitions and all outputs are combinational (do not require memory, as in the first state diagram of figure 3.14). This is the category, for example, in figures 3.1 and 3.2.

Category 2 (timed) FSM:    Contains one or more transitions that depend on time (see transition BC in the second machine of figure 3.14), but the outputs are still combinational. A separate circuit (a timer) is needed.

Category 3 (recursive) FSM:    Contains one or more outputs that are recursive (i.e., depend on previous output values; see y = y + 1 and y = y in states B and C respectively in the third machine of figure 3.14). A separate circuit (a register) is needed for such outputs because the outputs of an FSM are produced by the combinational logic block (memoryless). Timed transitions might or might not exist; if they do, a timer is needed.

The resulting hardware architectures are shown in figure 3.15. Category 1 (figure 3.15a) corresponds to the case already seen in section 3.10. Category 2 (figure 3.15b) contains an additional element, which is a timer, whose output, t, is an auxiliary input to the FSM, but it is the FSM itself who controls the timer (represented by the ctr signal). Finally, in category 3 (figure 3.15c), an auxiliary register is needed to store the recursive output(s); the timer might or might not exist. The output register of figure 3.15d might be required in any of these architectures, as explained in the Note of section 3.12.

Figure 3.15
Hardware architecture of (a) regular, (b) timed, and (c) recursive machines.

Details on the operation of timed and recursive machines are presented in sections 3.14 to 3.17. The number of flip-flops needed to build any machine is discussed in section 3.18. Finally, examples for all three categories are given in sections 3.19 to 3.21.

3.14    Dealing with Time

Since in timed machines it is the machine itself who is responsible for controlling the timer (to let it run or not; to zero it or not), a systematic approach is desirable for constructing the timer and the control signals. Two alternatives were proposed in the reference mentioned earlier, the main of which is summarized below and will be adopted in all designs.

Criteria for controlling the timer

1)  For stopping the timer: Stop the timer when it reaches the monitored value (or a predefined value above that), keeping it so until the machine changes its state. Keep the timer stopped (at zero) in the states where it is not needed.

2)  For zeroing the timer: Zero the timer when the machine changes its state.

To apply the timer-zeroing technique above (later, with VHDL), we can compare pr_state to nx_state; if they are different, it means that the machine will change its state at the next clock edge, so a flip-flop clearing command can be produced to zero the timer when that transition occurs.

Time unit and timer range    For circuits, the time unit is not seconds, but rather “number of clock cycles.” For example, if we want a circuit to produce a pulse with a duration of 3μs, and the clock frequency is 50MHz, the pulse must last Tpulse = 3 ● 10⁻⁶ × 50 ● 10⁶ = 150 clock periods. However, in the text, it is inevitable to refer to the pulse using seconds, so Tpulse can mean 3μs or 150 clock periods, depending on the context.

We will assume that the counter that implements the timer starts from zero. Therefore, to span T clock periods, it must count up to T − 1. In the example above, it would run from 0 to 149.

An example    The overall idea is illustrated in figure 3.16a, which shows an implementation using an FSM for the switch debouncer of section 2.11.2 (figure 2.28). Since this is a timed machine, a timer is needed, obeying the strategy described above. If the machine is in state zero, for example, and x = 1 occurs, it moves to state still_zero immediately (i.e., at the next positive clock edge), where the timer is released to start counting. If it reaches Tdeb − 1 (where Tdeb is given in “number of clock periods”), the machine moves to state one, where the output is raised to y = 1. On the other hand, if x returns to zero before the timer reaches Tdeb − 1, the machine returns to state zero, where the timer is zeroed and remains stopped at zero because, in that state, the timer is not needed. Because this FSM is symmetric, the change of x from 1 to 0 (lower path) obeys the same criteria.

Figure 3.16
(a) FSM that implements a debouncer for the switch in figure 2.28; (b) Illustrative timing diagram for Tdeb = 3 clock cycles.

Depending on the encoding scheme, y is subject to glitches (obviously, only immediately after clock transitions because the output of a Moore machine is synchronous), so the (optional) output DFF might be required. Ideally, a resetless circuit is desired in this kind of application, which can be achieved using minimal encoding (a sequential or Gray code, for example; the latter might require less logic when the machine is just a loop, as is the case here) because then all states belong to the machine, so it cannot get locked. Finally, notice that x is asynchronous, but because in actual debouncers Tdeb is thousands of clock cycles, a synchronizer is not needed.

An illustrative timing diagram is shown in figure 3.16b, where Tdeb is just three clock cycles, so a new value of x is transferred to y if it remains stable for three full clock periods (more precisely, for four consecutive positive clock edges). Time delays (gray shades) were included to portray a more realistic situation. The last plot, y_reg, shows the result in case the optional output register is added to this Moore machine (notice its latency of one clock period).

Note:    An important simplification, regarding the use of the timer’s MSB, was introduced in figure 2.29, so that implementation is preferred (though the same simplification could be included in figure 3.16a).

3.15    Dealing with Repetitive States

This section calls attention to a problem that is common when dealing, for example, with serial data transceivers, which often have states that must be repeated several times. Even though in some cases it might not be worth using the FSM approach for implementing such circuits, representing them as FSMs is usually still worthwhile (recall section 3.3).

The behavior of a circuit that, on receiving a start command, must transmit eight bits serially is depicted in figure 3.17a, where d is the data to be transmitted (retrieved from memory addresses 0 to 7) and dashed lines indicate “don’t care” values. A trivial (brute-force) solution using a regular machine is shown in figure 3.17b, where y is the output; though this solution is fine here, it is so only because the number of bits to be transmitted is small. A generic solution using a timed machine is shown in figure 3.17c. Notice that the latter achieves two purposes: first, it exposes the problem well; second, it tells the designer that this problem is simple enough not to use the FSM approach. (Hence drawing the state transition diagram was useful anyway.)

Figure 3.17
(a) Behavior of a serial transmitter over a single-bit channel; (b) Brute-force solution; (c) Solution using a timed machine.

A more complex case is depicted in figure 3.18, where a set of five bytes (instead of a single byte) must be transmitted. A timed machine can again be used. In figure 3.18a, a single extended timer is used, while a dual timer is employed in the (more elegant) solution of figure 3.18b.

Figure 3.18
Timed machines that transmit five consecutive bytes built with (a) a single extended timer or (b) a dual timer.

3.16    Pointer-Based FSM Implementation

Machines that fall in the class described in section 3.15 (big loops, often with repetitive states) can be implemented using simply a pointer (or more than one) and a lookup table (LUT), hence skipping the full FSM formalities. An example is shown in figure 3.19. Upon receiving a start command, which lasts one clock period, the machine in (a) must transmit one data byte to a receiving device, but it must be preceded by an 8-bit device-enabling opcode plus a 16-bit address informing where the received data should be stored. This solution uses a traditional timed machine. Observe carefully the indexing involving t in each state, where it is assumed that the MSB must be transmitted first.

Figure 3.19
(a) Conventional implementation for a data transmitter; (b) Equivalent construction using a pointer; (c) LUT for the latter.

An equivalent solution is shown in figure 3.19b, in which a pointer (i) is employed. It is simply a controlled counter that spans all states, hence ranging from 0 to 32 (written right below the state circles). Notice that the indexing involving i here is different from that involving t in the previous solution. A corresponding LUT is shown in figure 3.19c, so the problem gets reduced to two parts: implementing the pointer and implementing the LUT. Examples with VHDL using this construction option will be shown in chapters 16 and 17.

3.17    Dealing with Recursivity

In applications like datapath control it is common to encounter FSMs with recursive equations for the output (like the third FSM of figure 3.14). Even though that seems simple, the actual mechanism behind it is not as obvious as it looks. On the other hand, once we have understood it well, translating it to VHDL (in chapter 15) becomes straightforward.

There are two questions that concern only recursive machines. The first is how the machine should behave when it enters a recursive state; for example, if that state’s equation is i = i + 1, will i be incremented when the machine enters that state or only after it has entered that state? The second is even more critical: because the output of an FSM is produced by combinational logic, the output value is not memorized, so how can i = i + 1 be calculated if the machine does not remember what the value of i was?

The answer to the second question above is that i must be registered (i.e., stored in DFFs), producing i_reg, so the actual recursive equation becomes i = i_reg + 1, which is then implementable. The answer to the other (first) question is presented in the example below.

The operation of recursive machines is illustrated using the FSM of figure 3.20a, which models, with just three states, a generic-size counter, running from 0 to MAX and restarting then from zero while enabled (ena = 1). If disabled (ena = 0), the machine goes to a hold state, in which it stops counting but keeps its last value.

Figure 3.20
(a) A generic-size counter modeled as a recursive FSM; (b) Its combinational logic part that produces the output plus the auxiliary register; (c) Illustrative timing diagram.

The combinational logic portion (upper section of figure 3.15c) plus the auxiliary register for i are shown in figure 3.20b. In the multiplexer, 0, 1, and 2 represent the states zero, count, and hold, respectively; for example, when pr_state = count, the output is i = i_reg + 1.

With the help of figure 3.20b, the timing diagram of figure 3.20c can be easily drawn. So the answer to the first question above is that i starts incrementing when the machine enters the count state (see the plot for i). However, the actual output can be chosen between i and i_reg; the latter is one clock cycle behind the former, but it has a more predictable time behavior (and is glitch free, though that usually does not matter in this kind of application, particularly because glitches can only occur immediately after clock transitions anyway).

In summary, auxiliary registers are needed in recursive machines (one for each recursive signal) and the output can be chosen between the unregistered and registered versions, with a one-clock-period latency in the latter.

3.18    Number of Flip-Flops in FSMs

In general, and particularly in large designs, it is difficult to estimate the number of logic gates that will be needed to build the desired solution. However, it is always possible to determine, and exactly, the number of flip-flops (N), which is as follows, where S is the machine’s number of states.

1)  For the FSM proper (i.e., for the state register):

Using sequential or Gray encoding: Nfsm = ⎡log₂(S)⎤. Example: If S = 25, then Nfsm = 5.

Using Johnson encoding: Nfsm = ⎡S/2⎤. Example: If S = 25, then Nfsm = 13.

Using one-hot encoding: Nfsm = S. Example: If S = 25, then Nfsm = 25.

2)  For the timer (mandatory in timed machines): Ntimer = ⎡log₂(Ttimer)⎤, where Ttimer is the timer’s largest transition time, expressed in “number of clock cycles.” For example, if the largest time is 8μs and fclk = 50MHz, the value of Ttimer is 8 ● 10–6 ● 50 ● 10⁶ = 400 clock periods, so Ntimer = 9 DFFs.

3)  For the auxiliary register (mandatory in recursive machines): Naux = Baux, where Baux is the number of bits of the recursive signal(s) being registered.

4)  For the output register (optional in all machines; this role can be played by the auxiliary register in recursive FSMs): Nout = Bout, where Bout is the number of bits of the output signal(s) being registered.

Therefore: Ntotal = Nfsm + Ntimer + Naux + Nout flip-flops. These equations will be used in several of the examples that follow.

3.19    Examples of Category 1 (Regular) State Machines

From this point on we deal exclusively with examples of FSMs, covering all three categories. For category 1, examples were already seen in figures 3.1, 3.2, 3.4, 3.5, 3.6, and 3.9; additional examples are presented below.

3.19.1    Arbiter

Arbiters are used to manage access to shared resources. One of the simplest arbiter architectures is depicted figure 3.21a, where three devices (D1 to D3) share a common bus. Obviously, only one of them can use the bus at a time. If D1, for example, wants to use the bus, it issues a request (r₁ = 1) to the arbiter, which grants (g₁ = 1) access only if the bus is free. If multiple requests are received, access is granted based on preestablished priorities.

Figure 3.21
A simple priority-based arbiter: (a) General architecture; (b) Corresponding FSM.

An FSM that implements this arbiter is shown in figure 3.21b, with priorities D1 > D2 > D3. The machine’s input and output are the vectors r = r₁r₂r₃ and g = g₁ g₂ g₃, respectively (clk and rst not shown). (In VHDL, “don’t care” is officially represented by '−', though 'X' is also synthesized as such—section 7.6.2.)

Notice that this machine clearly falls in the situation described in section 3.3—that is, the state diagram is useful to expose the problem but, at the same time, it tells us that a solution can be easily implemented without using the (formality of the) FSM approach.

3.19.2    Garage Door Controller

This example deals with a garage door controller that operates as follows: if the door is completely closed or completely open and the remote control is activated, the motor is turned on in the direction to open or close it, respectively; if the door is opening or closing and the remote is activated, the door stops; if the remote is activated again, the motor is turned on to move the door in the opposite direction.

The circuit ports are depicted in figure 3.22a. The inputs are rem (pulse decoded from the signal received from the remote control, and hence synchronous to our clock and possibly lasting multiple clock cycles), closed and open (from door-closed and door-open sensors, already debounced; hence synchronous too), plus the operational signals of clk and rst. The outputs are motor (1 turns the motor on, while 0 turns it off) and dir (defines the motor direction, opening the door when 1 or closing it when 0). The value of the latter obviously does not matter when the former is zero.

Figure 3.22
Garage door controller: (a) Circuit ports; (b) Initial (poor) solution; (c) Final FSM (with wait states).

A preliminary state diagram is shown in figure 3.22b. Note that this machine exhibits a major problem, which is state bypass (section 3.8); for example, if the door is closed and a long rem = 1 command is received, the machine goes around in a loop. The diagram is corrected in figure 3.22c, containing now wait states, which wait for rem to return to zero (see states opening1 and opening3 in the door opening path, and states closing1 and closing3 in the door closing path) before proceeding. Even though glitches at the output (following clock transitions) might not be a problem in this kind of application, registering the outputs (just two flip-flops) is not a bad idea.

3.19.3    Datapath Control for a Greatest Common Divisor

The use of state machines to control datapaths is a popular application for FSMs. However, this kind of application is very different from the others seen so far because here we are not allowed to choose the circuit; instead, the circuit (datapath) is given, and the only thing that we can do is to play with it (using multiplexer-selection signals, read- and write-enable signals, etc.) such that the desired functionality eventually results.

One of the most common datapaths is the one that represents the architecture of a CPU, as in the example of figure 3.23a (based on MIPS). Observe its (typical) components: multiplexers, ALU, program counter, data and instruction memories, and other distributed registers. The control unit, which is responsible for producing the control signals (there are nine in this example), is commonly built using the state machine approach (many FSMs result, depending on the extension and complexity of the instructions set). Two partial FSMs, which are part of the control unit, are shown in figures 3.23b and c; they implement the store word (SW) and load word (LW) instructions.

Figure 3.23
Example of CPU datapath.

The datapath for the present example is shown in figure 3.24a, along with operation tables in figures 3.24b and c. It must compute the greatest common divisor (GCD) of two positive integers. The GCD algorithm is illustrated in the flowchart of figure 3.24d; the largest value is substituted with the difference between it and the other value until the values become equal, which is then declared to be the GCD. In our example, the computation must begin when a start pulse, lasting one clock period, is received.

Figure 3.24
(a)–(c) Datapath and (d) flowchart/algorithm for GCD calculation.

The datapath’s control signals are selA and selB (select the data sources for registers A and B), wrA and wrB (enable writing to registers A and B), and ALUop (ALU opcode, defining its operations, according with the tables in figures 3.24b and c). The control unit (FSM) is responsible for generating these signals.

An illustrative timing diagram for an FSM that controls this datapath such that the desired computations occur is presented in figure 3.25a (propagation delays were included to portray a more realistic situation). Dashed lines indicate “don’t care” values. The start pulse lasts only one clock period, but it is assumed that the input values remain valid for at least another period. Since the input values are inpA = 9 and inpB = 15, the following computations are expected: Iteration 1: 9 → A, 15 → B; Iteration 2: B > A, then 15 − 9 = 6 → B; Iteration 3: A > B, then 9 − 6 = 3 → A; Iteration 4: B > A, so 6 − 3 = 3 → B. Since A = B, GCD = A = 3.

Figure 3.25
(a) Illustrative timing diagram for the GCD of figure 3.24, with inpA = 9 and inpB = 15; (b) Corresponding FSM.

Observe also in figure 3.25a that the time slots are identified as idle (waiting for a start pulse), load (inpA and inpB are stored in registers A and B), writeA (ALUout is stored in A), and writeB (ALUout is stored in B). Observe also the presence of a wait time slot after every data storage, needed for the data to be effectively ready for comparison before an actual comparison occurs.

A corresponding state machine is shown in figure 3.25b and is a direct translation of the timing diagram just described (compare the values in the plots against those in the state transition diagram). Note that after each write-enabling state (load, writeA, and writeB) the machine goes unconditionally to state wait. In the idle state, we have wrA = wrB = 0, so nothing can be written to the registers, and because ALUop = 0, the output is ALUout = A, so the computed GCD value is kept unchanged until start is asserted again. As usual, “−” means “don’t care.”

3.20    Examples of Category 2 (Timed) State Machines

We have already seen examples of timed FSMs in figures 3.16 to 3.19. Additional examples are presented below.

3.20.1    Car Alarm

A simple car alarm was presented in figure 3.9. It is extended here to include chirps that announce when the alarm is turned on (one chirp) or off (two chirps). Because the chirps are brief siren activations, a timed machine is now needed.

The circuit ports are shown in figure 3.26a. The inputs are rem (pulse from the decoder of the remote control signal; hence synchronous to our clock) and sen (from the intrusion detection sensors, properly debounced; hence also synchronous), plus clock and reset. The output is siren, which must be turned on when an intrusion is detected or during the chirps.

Figure 3.26
Car alarm: (a) Circuit ports; (b) A timed Moore solution.

A corresponding Moore solution is presented in figure 3.26b, having disarmed, armed, and alarm as fundamental states, chirp1 to chirp5 as chirp-generating states, and wait1 plus wait2 as waiting states for the return of rem to zero in order to avoid state bypass. The chirp duration and the time interval between chirps are Ton and Toff, respectively. Notice that a “relaxed” notation was employed for the timed transitions; Ton and Toff are still the total number of clock cycles, so the counter that implements the timer must run from 0 up to Ton − 1 (not Ton) or Toff − 1 (not Toff).

Even though glitches at the output (they can only occur immediately after clock transitions) might not be a problem in this application, registering the output (a single flip-flop) is not a bad idea.

3.20.2    Password Decoder

This example concerns a password decoder, used (for example) in password-protected door locks, like that in figure 3.27a. The circuit ports are shown in figure 3.27b, with inputs key (representing the key pressed by the user) plus clock and reset, and outputs led1 and led2 (lit when the circuit is in the ready or unlocked state, respectively). The key signal is composed of 4 bits, so it can encode a keypad with up to fifteen keys (the codeword 1111 is reserved for the no-button-pressed case). It is assumed that the bits of key are already debounced.

Figure 3.27
(a) A password-protected door lock; (b) Password decoder ports; (c) Moore-type solution.

The desired circuit features are the following (where T = 3 seconds):

1)  When the system is in state ready, LED led1 must be on and LED led2 must be off. The opposite must occur in state unlocked. In all other states, both LEDs must be off.

2)  If the correct password is entered, led2 must be turned on for a time T (so the user has enough time to open the door), after which the system must return to state ready.

3)  If the user takes longer than T to press or release a key, it must be considered an error.

4)  It is indispensable that the machine not return to its initial state right after an error occurs because that would allow the password to be discovered.

5)  Finally, passwords must contain three digits, with repeated digits allowed.

A Moore-type solution for this problem is presented in figure 3.27c. The three digits that compose a password are called a, b, and c, while digit n (= 1111) indicates that none of the keys is pressed. Note that both LEDs remain off while a password is being typed in and that the circuit response (either an error, so led1 returns to 1, or a door unlocking command, so led2 = 1) only occurs after the third key has been entered (the purpose of the temporary states is to delay the response when an error has occurred).

Based on section 3.18, the number of flip-flops needed to implement this circuit is as follows:

For the state register: The number of states is S = 11, so Nfsm = 4 for sequential or Gray encoding, 6 for Johnson, or 11 for one-hot.

For the timer: Since ttimer = 3 s, and assuming that fclk = 50MHz, we obtain Ttimer = 15 ● 10⁷ clock cycles, so Ntimer = 28.

For the optional output register: We are already consuming 28 DFFs just for the timer, so using two more to obtain clean outputs is not a bad idea (the latency introduced by this register is definitely not a problem here). Therefore, Noutput = 2.

Consequently, the total is Ntotal = 34 or 36 or 41 flip-flops.

3.20.3    Serial Peripheral Interface for an A/D Converter

Appendix F summarizes how a serial peripheral interface (SPI) works. In this example, an SPI for an ADC is developed. The device (MAX1118, from Maxim) is a two-channel, 8-bit analog-to-digital converter, with the pinout shown in figure 3.28a. The figure includes also a list with the official SPI names and the corresponding device pin names. This device can operate with VDD from 2.7V to 5.5V and a reference voltage between 1V and VDD. The analog inputs can range from 0V (output = 00000000) to VREF (output = 11111111).

Figure 3.28
SPI interface for the MAX1118 A/D converter: (a) Chip pins; (b) Setup for the experiments; (c) SPI communication in this device.

Figure 3.28b shows a setup for the experiments, with VREF = VDD and a manually controlled (using a potentiometer) analog voltage applied to pin 2 (channel CH0). This circuit will be implemented later in an FPGA using VHDL (example 16.4 and exercise 17.1).

Figure 3.28c shows how SPI works in this device, where clk is the reference SPI clock (up to 5MHz, as indicated by the minimum period of 200ns in the figure). The communication starts when SSn is lowered, which selects the device. Because that occurs while SCLK is high, this is mode-3 operation (see figure F.2b of appendix F). SSn = 0 causes the device to start the A/D conversion immediately (using an internal clock), which takes about 7.5μs, after which the result (8 bits) can be retrieved serially through the MISO wire.

A timed Moore machine for this problem is shown in figure 3.29a. In the idle state, SSn and SCLK are both high, so the device is inactive. When a read (rd) command is received, the machine moves to the convert state, in which it remains for 38 clock periods (= 7.6μs, assuming fSCLK = 5MHz), so the data conversion can be properly completed. Next, it moves to the read_data state, where it remains for eight clock cycles, so the complete data byte can be retrieved. The retrieved data must be stored locally (as indicated by the box under the read_data state) or passed to another circuit. Finally, the machine moves to state hold, where it remains until rd is lowered (in case it is still high) before returning to the idle condition. Continuous reading is possible by removing the hold state and keeping the rd signal high.

Figure 3.29
FSM solutions for the SPI interface of figure 3.28 using (a) conventional and (b) pointer-based FSM implementations.

A second solution, with a different representation for the FSM and possibly implemented using the pointer-based approach (section 3.16), is presented in figure 3.29b (notice that the hold state, H, is not truly needed because the !rd condition could be at the output of state read_data).

3.21    Examples of Category 3 (Recursive) State Machines

An example of recursive machine was already seen in figure 3.20. Other examples are shown below.

3.21.1    SRAM Memory Interface

This example shows the design of a memory interface for writing to an asynchronous SRAM. As shown in figure 3.30a, the circuit inputs are data (to be stored in the memory) and dv (data valid, which determines the duration of the memory-write procedure), plus clock and reset, while the outputs are addr (address where data must be stored), CEn (chip enable, active low), and WEn (write enable, also active low).

Figure 3.30
Memory-write interface for an asynchronous SRAM: (a) Circuit ports; (b) Illustrative timing diagram; (c) Corresponding FSM.

The overall operation is illustrated in the timing diagram of figure 3.30b (propagation delays were included to portray a more realistic situation). Writing occurs while dv = 1. For CEn, an inverted version of dv can be used, so CEn does not need to be in the FSM. Each write cycle takes three clock cycles, with WEn pulsed low in the middle cycle (this is a conservative procedure; most SRAMs accept the address and data to be changed closer to the WEn pulse).

A Moore-type solution for this problem is presented in figure 3.30c, which is a direct translation of the timing diagram just described. The machine contains four states, called idle, write (the WEn pulse is generated), hold (WEn returns to one), and inc (the address is incremented). Notice the recursive equations addr = addr in states write and hold, and addr = addr + 1 in state inc.

3.21.2    Datapath Controller for a Serial Multiplier

As mentioned in section 3.19.3, state machines are often used in datapath control applications. In this example, a recursive machine is used to build a small control unit for a serial multiplier.

Figure 3.31a shows an algorithm for sequential multiplication of two non-negative integers using only add-and-shift operations. It computes the product in N iterations (after a data-load operation), where N is the number of bits in the multiplier and multiplicand and 2N is the number of bits in the product. The product is divided in two halves called prodL (product left) and prodR (product right). In this example, the inputs are 1100 (multiplicand = 12) and 1011 (multiplier = 11), so the expected result is 10000100 (product = 132).

Figure 3.31
Datapath and control unit for a serial multiplier: (a) Serial multiplication algorithm; (b) Elements of the datapath; (c) FSM for the control unit.

Initially, the multiplicand is stored in a (fixed) register and the multiplier is loaded in prodR, with prodL zeroed. The algorithm checks the LSB of the product; if it is 0, the product register is simply shifted to the right one position (empty position filled with the carry bit); however, if it is 1, mult is added (with carry) to prodL before the shift operation is executed. After N iterations, the product is available in the product register.

Figure 3.31b shows the parts of a datapath used to implement this multiplier, consisting of an ALU, a multiplexer, and two registers (REG1, REG2). It is assumed that it is a 16-bit system. The control unit (our FSM) must generate the signals wrR1 and wrR2 (to enable writing to REG1 and REG2, respectively; note that the FSM operates at the positive clock edge but that the actual writing occurs at the negative clock edge), sel (for mux input selection), ALUop (to control the ALU operation), and shift (to shift REG2 to the right one position, with carry). The ALU opcodes are listed in the accompanying table.

The multiplication starts when the control unit receives the start pulse (with duration of one clock period), on which it enables REG1 (by means of wrR1 = 1) to store the multiplicand, and REG2 (by means of ALUop = 0, sel = 1, and wrR2 = 1) to store zero in prodL and the multiplier in prodR. After this, wrR1 stays low until the end of the computations, while wrR2 is asserted at the end of each iteration to enable the storage of ALUout into prodL, after which shift = 1 is produced to shift REG2 one position to the right. After N of such iterations, the product will be available in REG2.

A Moore machine that implements the control unit for this datapath is presented in figure 3.31c, which is a direct translation of the sequence of manipulations just described. Recursivity can be observed in states load, nop, add (i = i in all three), and shift (i = i + 1). Though the start flag only lasts one clock period, in this solution it is necessary that the multiplicand and multiplier be (still) available in the next clock period because it is in that time slot (i.e., during the load state) that they are stored. (There is a signal in this FSM that is redundant, included just for clarity; can you identify it?)

3.21.3    Reference-Value Definer with Embedded Debouncers

Figure 3.32a shows a circuit that must set a reference value (as in section 2.11.3), in the REF_MIN to REF_MAX range, using up/down pushbuttons. Figure 3.32b shows an FSM that implements such functionality with the debouncers for the pushbuttons embedded in it, resulting in a recursive machine with timed transitions. For simplicity, REF_MIN and REF_MAX are called rmin and rmax in the figure. The initial value (upon reset) of ref is REF_MIN. Observe the transition defining signals, which can be just “updn” (in transitions AB, CB, DB, GB), “updn & ref” (in BC and BD), “updn & t” (in CE and DF), or even unconditional (in EG and FG). Notice also the presence of several “don’t care” (−) values.

Figure 3.32
Reference-value definer with embedded debouncer modeled as a recursive timed FSM.

4    Review of Field Programmable Gate Arrays (FPGAs)

4.1    Programmable Logic Devices

Programmable logic devices (PLDs) are a general denomination for integrated circuits whose hardware is programmable. They are different from microprocessors, for example, for which the tasks are programmable but for which the hardware is fixed; the hardware itself is programmable in a PLD, so that with the same device many different circuits (including microprocessors) can be implemented.

A major motivation for PLDs was the fact that at that time (1970s) most digital circuit boards were constructed with several (or many) devices from the 74xx series, as illustrated in figure 4.1, where the smaller devices are 74xx chips, which could potentially be replaced with a single PLD.

Figure 4.1
Replacement of several 74xx devices with a single PLD.

To achieve that purpose, programmable AND-OR arrays were employed as illustrated in figure 4.2a, where the little circles represent programmable connections, so different boolean functions can be implemented with the same hardware. This kind of implementation is called sum of products (SOP) because it consists of a product layer (AND gates) followed by a sum layer (OR gate). An example is shown in figure 4.2b, which implements the SOP y = a ● b + a′ ● b′ ● c ● d (black circles indicate that the wires are interconnected; of course, a logic 1 must be applied to the unused inputs of the first AND gate, while a logic 0 must be applied to at least one input of the third AND gate because it is not used in this example).

Figure 4.2
Illustration of programmable AND-OR array of first PLDs.

The first PLDs were introduced in the 1970s, as indicated in table 4.1, which summarizes their evolution. The first two approaches were called programmable logic array (PLA), introduced by Signetics in mid-1970s, and programmable array logic (PAL), introduced subsequently by Monolithic Memories. These devices, however, had major limitations—the main of which was the absence of flip-flops—so they were adequate for implementing only combinational circuits.

Table 4.1

Examples of features in top FPGAs from Intel and Xilinx

Such limitations were removed with the introduction of the generic array logic (GAL) approach, by Lattice, in early 1980s, which contains a “macrocell” at every output, with a flip-flop and several routing options (details will be shown in section 4.4). Some of such devices, like the GAL16V8 chip (notice that this is the PLD in figure 4.1), gained substantial popularity, finally bringing attention to the PLDs field.

Two companies, which eventually consolidated and today dominate the PLDs market, were founded around that time. The first (Altera, 1983, now part of Intel) introduced the complex PLD (CPLD) approach, which consists of relatively small arrays of moderate-size GAL-like blocks, still with nonvolatile configuration memory but with other features like more I/O options and more sophisticated clock and logic routing schemes. The second (Xilinx, 1984) introduced the field programmable gate array (FPGA—this term was popularized later, by Actel) approach, which consists essentially of a large matrix of small GAL-like macrocell clusters (hence a 2D array instead of the 1D array of previous PLDs), with the additional important differences that the programmable AND-OR array was replaced with a lookup table (LUT) and the nonvolatile configuration memory was replaced with a volatile version (SRAM). The first devices of these two companies, both delivered in 1984, are pictured in figure 4.3.

Figure 4.3
(a) First Altera CPLD (EP300) and (b) first Xilinx FPGA (XC2064), both from 1984.

All cases listed in table 4.1 are described individually next. However, before we start, the main strategies for holding the configuration data in PLDs are described.

4.2    PLD Configuration Memories

A PLD is able to produce different circuits because the routing among its cells is programmable (as in the example of figure 4.2b). The memory that holds such routing in place is called configuration memory (see the EEPROM inset in figure 4.4), which (as already mentioned) is nonvolatile in CPLDs and typically volatile in FPGAs. The technologies employed to build such memories are listed in table 4.1; in summary, SPLDs and CPLDs employed mostly EEPROM in the beginning and then Flash memory, while FPGAs employ mostly SRAM, with nonvolatile memory (Flash or antifuse) used in just a few device families of some companies. Brief comments on each technology are presented next.

Figure 4.4
General construction principle of (a) PAL and (b) PLA devices.

The SRAM alternative, used in nearly all FPGAs, has the (minor) disadvantage of losing the configuration when the power is turned off. To solve that problem, FPGA companies sell low-cost nonvolatile memory to store the configuration data, which is retrieved and loaded automatically by the FPGA itself every time the power is turned on.

The EEPROM approach, used in early PLDs, was replaced with Flash EEPROM (or Flash, for short), which has the advantage of requiring just one transistor per cell, against two of EEPROM (Flash does not need the cell-addressing transistor).

A less popular approach is antifuse technology, introduced by Actel (now Microsemi), in the late 1980s. Contrary to regular fuses, which melt and so disconnect two wires when traversed by a large current, an antifuse connects them because the material, after melting, becomes conductive. Another antifuse technology was introduced later by QuickLogic (which refers to it as ViaLink), which connects two metal layers when melted instead of silicon-polysilicon layers of Actel’s original antifuse.

Because SRAM, contrary to Flash and antifuse, can be manufactured in conventional CMOS processes, it has lower cost and can take full advantage of process advancements. Consequently, like Xilinx’s and Intel’s, nearly all FPGAs are now SRAM-based, including the general-purpose ones from Microsemi and QuickLogic.

4.3    PAL and PLA Devices

Figure 4.4 illustrates the general architectures employed in PAL and PLA devices. In (a), a programmable AND array is connected to a nonprogrammable OR gate, while in (b) both are programmable. As already seen in figure 4.2, the little circles indicate programmable connections for which EEPROM was the typical solution.

Because the inputs to the OR gates are also programmable in PLAs, a larger variety of boolean functions could be implemented than in arrays of same size in PALs. On the other hand, the parasitic resistance and capacitance of the associated long lines caused PLAs to be slower (besides consuming more silicon space). Nevertheless, due to the limitations mentioned in section 4.1, neither approach was truly successful.

4.4    GAL Devices

GAL caused a major advancement in the acceptance of PLDs. It employed the PAL architecture plus a “macrocell” at each output. As already mentioned, a successful device in this approach was the GAL16V8 PLD, depicted in figure 4.5a (and also at right in figure 4.1), with sixteen inputs and eight outputs in a twenty-pin package (eight pins are bidirectional).

Figure 4.5
The Lattice GAL16V8 device: (a) General structure; (b) Macrocell.

Details of the macrocell are shown in figure 4.5b. Note the following (very important) improvements over PAL alone: an option for registered or unregistered output (due to the flip-flop); choice of inverted or non-inverted output (due to the XOR gate); tri-state output; output sent back to the programmable array (so signals do not need to go out of the chip and return when complex functions must be implemented, which lowers the speed and consumes user pads); and direct connection between adjacent cells (with similar benefits).

Even though GAL too is now only of historical interest, understanding it is crucial because, as will be seen, CPLDs and FPGAs are both related to GAL.

4.5    CPLD Devices

As seen in table 4.1, the CPLD approach was introduced by Altera, with its first device (EP300, figure 4.3a) delivered in 1984. A simple way of describing a CPLD is as a relatively small array of moderate-size GAL-like units (figure 4.6), still with nonvolatile configuration memory but with some additional features compared to GAL, like superior internal interconnects, more elaborate clock network, and more I/O options.

Figure 4.6
Illustration of general CPLD architecture.

An example of true CPLD family (hence employing the general architecture of figure 4.6) is the old MAX3000 series from Altera, constructed with n = 2, 4, 8, 16, or 32 GAL-like structures, each with sixteen macrocells (Altera called such structures logic array block, or LAB). Consequently, the total number of macrocells (and therefore of flip-flops) in this family ranged from 32 to 512. Another example of true CPLD family, this time from Xilinx, is the old XC9500 series, constructed with n = 2, 4, 6, 8, 12, or 16 GAL-like structures, each with eighteen macrocells.

A major weakness of PAL-like devices (hence all PLDs seen so far) is that they do not scale well with technology. Note in figure 4.4a that the number of programmable points quadruples when the number of inputs (and the size of the AND layer) doubles; consequently, the device’s growth with the transistors’ shrink factor is just linear instead of quadratic (for instance, the resulting long, heavily loaded lines impact the speed and power consumption). As a result, the original CPLD approach is now practically gone. Companies either do not manufacture them anymore or use “simplified” FPGAs in their place, with nonvolatile memory employed for the configuration switches (SRAM is replaced with Flash memory); so from a user’s perspective everything looks the same. Examples of such “CPLDs” are the Altera MAX II, MAX V, and MAX 10 families, which are now (appropriately) referred to by Intel as FPGAs instead of CPLDs. In summary, these are just simpler, lower-cost FPGAs but with nonvolatile configuration memory (and the programmable AND/OR array replaced with a LUT, of course).

4.6    FPGA Devices

Contrary to CPLDs, the matrix-like (2D) architecture of FPGAs, with the large AND-OR arrays (and their long, slow lines) replaced with compact LUTs, plus the use of SRAM in place of Flash for configuration, allow FPGAs to scale well with technology, fully benefiting from the huge progress in device fabrication processes. (Recall also that an N-input LUT can implement any N-variable Boolean function, which is not true in general for N-input AND-OR arrays.) Indeed, still in the 1990s, FPGAs started replacing (small) ASICs. Eventually, with the ample adoption of FPGAs in the communications infrastructure worldwide (for instance, Cisco is among the largest FPGA consumers), FPGA became definitely a major player, being now present in all sorts of applications, like machine learning, real-time video processing, automotive, data centers, high-performance computing, etc. As a result, the PLDs market now consists essentially only of FPGAs, described in this section.

What was and what is an FPGA    Figure 4.7a illustrates how FPGAs looked like in the beginning. Its core, the programmable logic array, was a relatively modest matrix of small GAL-like macrocell clusters, but with LUTs in place of AND-OR arrays for computing the logic functions and with SRAM in place of Flash for configuration. The additional features consisted essentially of some user SRAM blocks, hardware multipliers (for DSP applications), the indispensable PLLs (for clock manipulation, section 2.8), and a selection of basic I/O standards (3.3V LVCMOS, LVDS, etc.).

Figure 4.7
(a) FPGAs in the beginning and (b) FPGAs today.

The current situation of FPGAs is illustrated in figure 4.7b. The programmable logic array is much larger, and so are the original features (listed under the logic array block). But even more importantly, a number of very sophisticated units are now also available (listed at right in figure 4.7b), including high-bandwidth memory (HBM), with the largest throughput in the industry, the fastest transceivers in the market (for internet infrastructure, for example), hard IPs for several communications standards (PCI Express, 10G XAUI, 100G Ethernet, etc.), hard ARM processor cores (for general-purpose computing), and so on. In summary, an FPGA is now a complex “ecosystem,” fabricated using the latest technology and targeting all sorts of applications, as mentioned previously.

Below are some major features or advantages of FPGAs:

-  They allow the construction of solutions that would be technically inferior or economically unviable with other approaches, notably when units from the list at right in figure 4.7b are also involved.

-  Solutions can be tailored exactly as needed in the target application. This helps system optimization, like higher speed through parallelism and lower power consumption by proper circuit architecture and proper clock management. They are also great for doing math!

-  Designs are developed much faster and do not have the risk and non-recurring engineering (NRE) cost of ASIC-based solutions.

-  Contrary to ASICs, designs can usually be easily modified. This might be needed to comply with new standards or different application parameters.

-  Contrary to CPLDs, they can easily benefit from progress in device fabrication technology.

-  And, surprisingly, they are easily programmed! VHDL and Verilog languages, plus current compilers and simulators, make this task very accessible. Simulation is also much simpler than in ASICs because the hardware has already been validated.

The programmable logic array    The programmable logic array block seen in figures 4.7a and b is described next. Figure 4.8 summarizes the current Intel architectural approach—used, for example, in its top FPGA family, Stratix 10. It consists of a large matrix of LAB units, shown in (a). The contents of each LAB are shown in (b), consisting of 10 adaptive logic module (ALM) units. Finally, the contents of each ALM are depicted in (c), where the LUT (with eight inputs, separable into two or more smaller LUTs) can be observed, followed by muxes and flip-flops, which allow the outputs to be registered or unregistered and also allow direct connection between adjacent units—helpful, for example, in the construction of fast arithmetic circuits. Notice the overall similarity of purposes between this circuit and the original macrocell (figure 4.5b).

Figure 4.8
Intel architecture for the logic part of Stratix 10 and other FPGAs: (a) FPGA grid (array of LAB blocks); (b) LAB contents; (c) ALM contents.

Figure 4.9 summarizes the current Xilinx architectural approach—used, for example, in its top FPGA family, Virtex UltraScale+. Note in (a) that the general architecture is the same as that in figure 4.8a, just with a different name for the logic block, here called configurable logic block (CLB). The contents of each CLB are depicted in (b), consisting of two Slice units. Finally, the contents of each Slice are shown in (c), where a LUT, muxes, and flip-flops can again be observed in each of the eight circuits that comprise one Slice. For simplicity, these circuits too will be individually (and informally) referred to as “macrocells” in table 4.4, shown later.

Figure 4.9
Xilinx architecture for the logic part of UltraScale+ and other FPGAs: (a) FPGA grid (array of CLB blocks); (b) CLB contents; (c) Slice contents.

In summary, each LAB (figure 4.8) contains ten LUTs and forty DFFs, while each CLB (figure 4.9) contains sixteen LUTs and thirty-two DFFs. These are standard cells, so what changes from one FPGA to another of the same family regarding the FPGA logic array is just the number of LABs or CLBs in the (big) matrix.

The construction of LUTs is illustrated in figure 4.10 (for N = 3 inputs), consisting simply of an SRAM memory (of 1-bit words) plus a multiplexer. Because the memory contains 2N addresses, any N-bit function can be implemented by it. For instance, note that the SOP implemented in figure 4.10 for the given SRAM contents is y = a ● b + a′ ● b′ ● c.

Figure 4.10
LUT construction principle (for 3 inputs, here computing y = a ● b + a ′ ● b′ ● c).

FPGA families    We list next details regarding the FPGA families from the two main vendors. Table 4.2 shows all current Intel families, which are (from lowest to highest end) MAX, Cyclone, Arria, and Stratix. It shows also the last two generations (V and 10) for each family and the corresponding technologies. Note that the MAX devices employ old technologies (180nm and 55nm), while Cyclone and Arria employ relatively new technologies (28nm and 20nm), and Stratix 10 employs an even newer node (14nm). Observe also that the division in series depends mainly on the additional features, of which the presence of transceivers and/or hard microprocessors are the main dividing factors; for example, when a microprocessor (always one or more ARM cores) is built into the FPGA fabric, that FPGA series is referred to as an SoC (system-on-chip) FPGA, because of the easiness with which complete complex systems can be built in that device.

Table 4.2

Intel FPGA families

Table 4.3 shows all current Xilinx families, which are (from lowest to highest end) Spartan, Artix, Kintex, and Virtex. It shows also the Zynq family devices, which are referred to as SoC FPGAs because of the presence of hard microprocessors (again, ARM cores). The last three Xilinx generations are 7, UltraScale, and UltraScale+, which employ technology nodes 28nm, 20nm, and 16nm, respectively.

Table 4.3

Xilinx FPGA families

Table 4.4 shows examples of important features in two top devices of similar categories, the first (Stratix 10 GX) being from Intel and the second (Virtex UltraScale+) from Xilinx. (The reader is invited to check the construction of the first seven lines against figures 4.8 and 4.9.) This table is not a comparison because there are many differences that cannot be listed in a simple table, but it is interesting to see that things are not much different from one to the other. For example, notice that the total numbers of LUTs, flip-flops, and equivalent logic elements overlap nicely. Observe also the similarity in the other features, like the core operating voltage and the maximum reference clock frequency. Finally, observe the variety and abundance of resources (and this table shows just some of them) in this kind of device.

Table 4.4

Examples of features in top FPGAs from Intel and Xilinx

Feature

Stratix 10 GX (fig. 4.8)

Virtex UltraScale+ (fig. 4.9)

Name of main logic block (figs. 4.8, 4.9)

LAB (logic array block)

CLB (configurable logic block)

Name of logic sub-block (figs. 4.8, 4.9)

ALM (adaptive logic module)

Slice

Number of LABs or CLBs

12.8k to 186.7k LABs

24.6k to 108k CLBs

Number of ALMs or Slices

10 ALMs per LAB

2 Slices per CLB

Number of “macrocells”

1 per ALM

8 per Slice

Number of LUTs per “macrocell”

1 (total: 128k to 1867k)

1 (total: 394k to 1728k)

Number of flip-flops per “macrocell”

4 (total: 512k to 7470k)

2 (total: 788k to 3456k)

Number of equivalent logic elements

378k to 5510k

862k to 3780k

Logic elements per “macrocell” ratio

2.95

2.19

Block memory (Mb)

30 to 229

115 to 454

Number of transceivers

24 to 96 (17 and 30 Gbps)

32 to 128 (32 and 58 Gbps)

Number of PCI Express ports

1 to 4

1 to 6

Number of user I/O pins

392 to 1640

208 to 832

Core operating voltage

0.8V to 0.94V

0.72V to 0.9V

Maximum reference clock speed (MHz)

800

800

CMOS technology

14nm

16nm

Price of FPGAs    We close this section with comments on FPGA prices, which can go from about a dollar up to tens of thousands of dollars. A simple, informal illustration for price ranges is presented in figure 4.11, where some numbers, covering the interval from $1 to $50,000, are employed to help visualize the big picture. For example, current Cyclone, Spartan, and Artix devices fall typically in the first range, while current Stratix and Virtex devices usually fall in the upper part of the second range and in the third range. It is important, however, to emphasize that devices in the low-cost range can be very powerful; for example, the initial Cyclone 10 GX FPGA, which contain 31k ALMs (85k equivalent logic elements), six high-speed (12.5 Gbps) transceivers, 6 Mb of user RAM, plus many other features, can be purchased for just over $100. As additional examples, the initial Spartan 7 FPGA, with 6k equivalent logic cells, can be bought by under $12, and MAX 10 FPGAs can cost less than $5.

Figure 4.11
An illustrative, informal representation for FPGAs’ price ranges.

The FPGA industry also offers a large selection of development boards, with some having special prices for academic use. Two low-cost examples (under $100) are depicted in figure 4.12, for a Cyclone 10 FPGA on the left and for a Spartan 7 FPGA on the right.

Figure 4.12
Examples of low-cost FPGA development boards (with Cyclone 10 on the left and Spartan 7 on the right).

I am grateful to Stephen Trimberger, and his 30-year experience with Xilinx, for kindly reviewing and helping improve this chapter. The reading of his paper “Three Ages of FPGAs: A Retrospective on the First Thirty Years of FPGA Technology” (Proceedings of the IEEE, vol. 3, March 2015) is highly recommended.

5    Introduction to VHDL

This chapter describes what VHDL is and what it is used for, along with information on design flow and commercial tools for VHDL synthesis and simulation. We also take our first steps into the VHDL language by describing its lexical elements.

5.1    About VHDL

VHDL is a powerful hardware description language. It can be used for both circuit synthesis and circuit simulation. In the case of synthesis (construction), the language describes the desired behavior or structure of a digital unit, from which a compliant physical circuit is inferred by the compiler. In the case of simulation (testing), the language generates stimuli and applies them to the designed (coded) circuit, subsequently verifying its behavior by comparing the obtained responses against expected results, immensely easing the verification (and documentation) of the circuit under development.

The main applications of VHDL include synthesis/implementation of digital circuits onto field programmable gate array (FPGA) chips and layout/mask generation for application specific integrated circuit (ASIC) fabrication plus corresponding functional and timing simulations.

VHDL stands for VHSIC Hardware Description Language, and resulted as part of the very high speed integrated circuits (VHSIC) initiative funded by the U.S. Department of Defense in the 1980s. Its first version was VHDL-87; it was later upgraded to VHDL-93, then to VHDL-2002, and finally to VHDL-2008. Even though the latest version is now several years old, many of the proposed new features were only recently added to popular VHDL software tools, while some features still wait implementation—or proved not to be needed.

5.2    Translation of VHDL Code into a Circuit

The top-level diagram of a full-adder unit (section 1.5.1) is shown in figure 5.1a, for which a compliant physical circuit must be constructed. The inputs are a and b (bits to be added) and cin (carry-in bit, to be also added to a and b), while the outputs are sum (the sum bit) and cout (the carry-out bit). Two possible descriptions for this problem are shown in figures 5.1b and c; in the first, the solution is expressed by means of a truth table, while in the second it is expressed by the corresponding boolean equations.

Figure 5.1
(a)–(c) A full-adder unit and two functional descriptions; (d) A corresponding VHDL code using the option in (c); (e)–(f) Two implementation alternatives, at gate and transistor level, respectively.

A VHDL-based solution for this problem is shown in figure 5.1d. It is composed of an entity declaration (or entity, for short), called full_adder_unit, in which the circuit ports are specified, and of an architecture body (or architecture, for short), called boolean_equations, in which the circuit behavior or structure is described (note that the option of figure 5.1c was adopted in this architecture). Upon compilation of this code, a circuit that complies with the full-adder unit functionalities is inferred.

The inferred circuit depends on the target technology (the target FPGA or ASIC library, for example). Two examples of solutions are depicted in figures 5.1e and f, at gate and transistor level, respectively.

5.3    Design Flow

A typical design flow is summarized in figure 5.2. The register transfer level (RTL) entry (VHDL code, in our case) is shown at the top, followed by four main steps (see, for example, the Vivado and Quantus Prime tutorials, in appendices A and B, respectively).

Figure 5.2
Typical design flow in FPGA-based projects.

The first step is RTL analysis and elaboration. In it, the VHDL code is compiled independently of target device or technology. The VHDL syntax is checked and the circuit schematic, as understood by the compiler, is generated, which helps verify whether our code actually expressed what we intended. Additionally, the circuit can already be simulated, further helping code verification. Simulation at this level (RTL) is usually called behavioral simulation, which falls in the functional simulation category. This step is particularly important in large/complex designs because this compilation plus simulation are faster than the ones that follow, so the code can be fixed before proceeding.

The second step is synthesis. In it, simplification might occur, primitives are used, and a netlist, which is now device/technology dependent, is created. Important information about hardware usage can already be seen. Both functional and timing simulations are available after this step, though they can be left for after the next step (implementation).

The third step is implementation (fitting). The design is assigned to particular logic blocks inside the FPGA and routing is performed. Both functional and timing simulations are again available, the latter being indispensable because it provides fundamental information about the finalized design (like propagation delays and the circuit’s maximum speed).

The last step is device programming. Usually, two files are generated for that purpose; these can then be downloaded by the user directly to the target FPGA (for direct programming, using one of those files) or to an external nonvolatile memory associated to the FPGA (for self-programming upon power up, using the other file), leaving the FPGA ready for physical tests and use.

Both functional and timing simulations are illustrated in figure 5.3, which shows results obtained from the circuit synthesized with the VHDL code of figure 5.1d for a certain FPGA. In functional simulation (figure 5.3a), only the circuit functionalities are tested, with zero propagation delays, whereas in timing simulation (figure 5.3b), propagation delays are taken into consideration. Important circuit parameters, like maximum speed, can obviously only be obtained from the latter.

Figure 5.3
(a) Functional and (b) timing simulation results from the circuit synthesized with the VHDL code of figure 5.1d.

5.4    Commercial VHDL Tools

There are several electronic design automation (EDA) tools available for circuit synthesis, implementation, and simulation using VHDL. Some tools are offered by FPGA companies (like Altera—now Intel FPGA—and Xilinx), while others are offered by third-party software companies. Some examples are listed below.

– From Xilinx: Vivado (synthesis/implementation and simulation).

– From Altera: Quartus Prime (synthesis/implementation and some simulation).

– From Mentor Graphics: ModelSim (simulation), Questa (simulation), Precision RTL (synthesis). ModelSim is indeed the tool that implements the simulation part in Quartus Prime and, optionally, also in Vivado.

– From Synopsys: Synplify Pro (synthesis).

– From Aldec: Active-HDL (simulation), Riviera-Pro (simulation).

All designs presented in the book were implemented and simulated using one or more of the following: Quartus Prime, Vivado, and ModelSim. Tutorials for these three tools are presented in appendices A to C.

5.5    RTL Design Approach

Synthesis with the VHDL language is based on the RTL abstraction approach. Therefore, before proceeding, a quick review of section 2.5 is suggested.

5.6    Concurrent versus Sequential Statements

A major feature of hardware description languages is concurrency. VHDL statements are concurrent rather than sequential, so a VHDL description is called a code rather than a program. Concurrency implies that the individual statements can be written in any order, as in the following example, where all three pieces of code would lead to the same result:

A common mistake is to assign a value to a signal somewhere in the code and then assign another value to it later, believing that the compiler will simply consider the last value as the valid one. Because the same circuit must result from any order of the concurrent statements, a value can be assigned to a signal only once, and it is a conceptual mistake to believe that that is a limitation of VHDL. The example below is not synthesizable because there are two assignments to sum:

The only exceptions in VHDL are processes and subprograms, inside which the code is interpreted sequentially (as in regular computer programs), so multiple assignments to the same signal are fine. Consequently, if the code above in placed inside a process, as shown below, it is synthesizable, and the last value prevails:

As will be seen in chapter 10, purely concurrent code (i.e., without processes) is employed only to build combinational circuits, though for complex combinational circuits, the use of processes can be helpful. For sequential circuits, processes are always needed.

5.7    Lexical Elements of VHDL

This section describes the lexical elements of VHDL (based on chapter 15 of the IEEE Std 1076 VHDL Language Reference Manual, 2011–05).

5.7.1    Assignment Symbols

To assign a value to a signal, the delimiter <= is employed (the complete list of delimiters is shown in section 5.7.7), while assignments to variables, constants, and initial/default values employ the delimiter :=.

Examples:

5.7.2    Comments

A single-line comment starts with two hyphens (--) and extends up to the end of the line; a delimited comment extends from /* up to */, in single or multiple lines.

Examples:

5.7.3    Bit and Bit String

Single-bit binary values are written within single quotes, while bit strings (bit vectors) are written within double quotes. The letter b or B (VHDL is not case sensitive) can be optionally used before the bit string to inform that the representation is binary (base 2). The underline character can be used as a separator to improve readability, but then the b or B letter is necessary.

Examples:

Octal and hexadecimal formats are also allowed. An o or O (for octal) or an x or X (for hexadecimal) must then precede the bit string.

Examples:

Negative values are usually represented in two’s complement format (section 1.6.3), so when the most significant bit (MSB—always the leftmost bit in VHDL) is '0', the value is zero or positive, while when it is '1', the value is negative (but recall that the MSB is not a sign bit in two’s complement).

Examples:

Bit string, along with character, character string, integers, and reals are called VHDL literals.

5.7.4    Integers

Contrary to bit and bit-string values, integers are written without quotation marks. The type called integer in VHDL operates with up to 32 bits, ranging from 0 to 2³¹ − 1 if unsigned or from −2³¹ to 2³¹ − 1 if signed. The underline character (_) can be used anywhere in the string to help visualize its value. Exponents are also allowed.

Examples:

Though unusual, other bases between 2 and 16 are also allowed. The base value must then precede the number, which must be surrounded by the pound character (#).

5.7.5    Character and Character String

The synthesizable characters in VHDL are the 256 characters from the ISO 8859–1 (Latin-1) table, in which the initial 128 characters are from the ASCII table. Following the same style of bit and bit string, a single character is written in single quotes and a string of characters is written in double quotes.

Examples:

5.7.6    Identifiers

Identifiers are used to name VHDL items, like signals, variables, entity declarations, architecture bodies, package declarations, etc. For a name to be valid, it must obey the following:

– It can contain only letters (a to z, A to Z), decimal digits (0 to 9), and the underline character (_);

– It must start with a letter;

– It must not have two adjacent underline characters or end with an underline character;

– It must be different from any of the reserved VHDL words (section 5.7.8).

Examples of legal names: clk, clk_5MHz, data_ready, NUMBER_OF_BITS.

Examples of illegal names: !rst, ena_, 4input_nand, return.

Since VHDL is not case sensitive, reset and RESET are considered the same name. An exception is when a name is surrounded by backslashes (then called extended identifier); for example, \clk\, \CLK\, and \Clk\ are considered different names.

5.7.7    Delimiters

Delimiters are used to mark the end of a statement or of a language construct, to represent operators, to enclose range specifications, and so on (several of them were already used above). They can consist of a single character (simple delimiter) or of a string with two or three characters (compound delimiter). The complete lists are shown below (special symbols # and " not included).

Simple delimiters:

Compound delimiters:

5.7.8    Reserved VHDL Words

Finally, table 5.1 shows the reserved VHDL words. The user is not allowed to use them to name an item (section 5.7.6).

Table 5.1

Reserved VHDL words

5.8    Choosing Good Names for Your Design

We close this introductory chapter with suggestions on how to pick good names [5] for VHDL design elements. Though VHDL allows the use of basically any name (the only exceptions are those seen in section 5.7.6), some names are certainly better than others.

5.8.1    Naming an Entity Declaration (“The Design”)

As shown in figure 5.1d, a VHDL code contains two main parts: entity and architecture. The name of the entity is usually also the name of the design as a whole, so it should be chosen carefully. Names like circuit, example, and test do not convey any information about the design’s nature or functionalities, so they should be avoided. In contrast, names like adder_array, pseudo_random_gen, frequency_meter, and dvi_video_interface make the purpose of the circuit very clear, so they are good choices. Depending on the context, particularly for subcircuits, simpler names like adder and counter might also be fine despite the many ways in which adders (chapter 1) and counters (chapter 2) can be implemented.

A special case is the construction of standard cells. Because reusability is then a major issue, the qualifier should be at the end of the identifier. For example, when building a collection of adders, the word adder should come first, followed then by the qualifier, as in these examples: adder_signed (adder built with ports of type signed), adder_unsigned (with type unsigned), adder_slv (with type std_logic_vector), adder_slv_registered (with type std_logic_vector and registered output), adder_carry_ripple (here the qualifier identifies the hardware structure), and so on. In isolated (and probably not to be reused) designs, having the qualifier at the beginning of the identifier is fine, as in registered_adder, eight_bit_serializer, and so forth.

5.8.2    Naming an Architecture Body

Though the architecture’s name can be the same as the entity’s name, more informative names should be preferred in general. A common option is to use a name that indicates the abstraction level of the hardware description, like behavioral (algorithmic kind of description, with no concern regarding the underlying hardware), dataflow (boolean kind of description, for combinational circuits), rtl (collection of combinational blocks interconnected via registers), and structural (architecture constructed using component instantiations).

Another naming approach is to emphasize particular implementation or behavior aspects, like combinational (indicating simply that it is a purely combinational circuit), boolean_equations or boolean (circuit implemented using its boolean equations explicitly), lookup_table, lut, or truth_table (circuit implemented using its truth table explicitly), and fsm (sequential circuit implemented using the finite state machine approach, reviewed in chapter 3).

5.8.3    Naming Constants

By convention, only capital letters will be used for constant names, which as usual must be informative, as in the following examples: NUM_BITS or WIDTH (number of bits in a bit vector), CLK_FREQ or F_CLK (clock frequency in Hz), CLK_FREQ_MHZ or F_CLK_MHZ (clock frequency in MHz), CLK_PER or T_CLK (clock period), and MAX_COUNT or COUNT_UPPER_BOUND (upper limit of a counting process).

The use of BITS instead of NUM_BITS, though poorer, is well known and hence acceptable. However, using BIT_COUNT instead of NUM_BITS is a little misleading because that name suggests a dynamic (counting) process rather than a static parameter, so it would be better as a signal or variable name (bit_count).

5.8.4    Naming Signals and Variables

Well-established abbreviations from digital circuits analysis or the entire original names (when they are short) are both good name choices, as in the following examples: clk or clock, clk_5MHz or clock_5MHz, rst of reset, rst_n or reset_n (for active-low reset), ena or enable, and clr or clear. (Note: In the context of this book, an asynchronous reset is called reset or rst, while a synchronous reset is called clear or clr.)

The use of single-letter names, like x (for input) and y (for output), is fine only for simple cases. Many circuits have more than one input and/or output, with already well-known identifiers, as in the case of memory interfaces, where the signals might be called data (or data_in and data_out if with separate data buses), address, write_ena, read_ena, and so on. Other examples, for more generic signals, are start, stop, count, data_valid, num_bytes_received, temp_register, bit_count, word_count, is_zero, and so forth.

5.8.5    Naming Functions and Procedures

A function is always part of an expression, returning a boolean (true or false) or a numeric value. When it returns a boolean, an assertion can be used for the function name, as in the following examples (use the if statement to help check whether it is a good name):

When the function returns a numeric value, a good option is to name it based on the operation that it executes, as in the examples below (use a signal assignment to help check whether it is a good name):

Some names are fine for both categories above, like the following:

Procedures, on the other hand, are standalone statements. A typical way of naming a procedure is to start with a verb that represents its action, as in the following examples:

5.8.6    Naming Types

Figure 5.4 illustrates how types are structured in VHDL. A single element of type bit is shown in (a). In the predefined types, when a one-dimensional (1D) array of a certain type is constructed, the suffix _vector is added to the base type name, and that is the reason why the type in (b) is called bit_vector. The same reasoning applies to the cases in (c) and (d), where the base type is integer (with up to 32 bits). The case in (e) is not included in the predefined types, so integer_matrix is a user-defined type.

Figure 5.4
Type structures for (a)–(b) bits and (c)–(e) integers.

The first suggested naming approach for types is to base the new name on the base type’s name (as in figure 5.4). Its advantage is that it exposes the kind of type we will be dealing with in the code that follows. However, using a generic suffix—like _array (as shown between parentheses in figures 5.4d and e) instead of _vector, _matrix, and so on—is also a good alternative because the array’s dimensionality can be easily deduced from the type declaration. Moreover, if we have a vector of std_logic_vector elements, for example, and we follow the original naming procedure, the resulting name is std_logic_vector_vector, which is definitely not a good name (this could be fixed using an abbreviation, like slv_vector, but using std_logic_vector_array or slv_array is better in general).

The second naming approach is based on the target circuit. For example, if we need to design a digital filter (sections 2.11.6 and 2.11.7), we could call a type for the internal nodes as filter_coefficients_type. Note that the suffix _type here is important because then the name filter_coefficients is free to be used for the signals or variables of this new type.

A major example in the second naming approach above is the case of enumeration types, particularly for finite state machines. In the example below, the type for the machine’s states is called state_type, so the name state is free to be used for the signal that actually represents the machine’s state:

5.8.7    Naming Files

Finally, we mention good names for the design files. Save synthesizable VHDL code in a file with the same name as the code’s entity declaration, with the extension .vhd. For example, if the entity is called frequency_meter, then the suggested file name is frequency_meter.vhd.

If a corresponding simulation (testbench) file is also produced, save it with the same name as the synthesizable file plus the _tb suffix. For example, if the entity is called frequency_meter, the name suggested for the testbench file is frequency_meter_tb.vhd.

6    Code Structure and Composition

This chapter describes how VHDL code for synthesis is assembled and how each of its parts is composed. Recall that everything in this book is synthesizable, with the exception of chapter 18, which concentrates all that is specific for simulation.

6.1    Design Units and Code Structure

Figure 6.1 depicts the main VHDL design units. On the left is the design entity (our VHDL code), which is composed of two parts, called entity declaration and architecture body. The code starts with a list of all packages needed to process that design.

Figure 6.1
Structure and composition of VHDL code.

On the right is the other design unit, the package, which is also composed of two parts, called package declaration and package body (the latter might not be required). Here too other packages might be needed, in which case they are again listed at the beginning of the code.

A brief description for each block of figure 6.1 is presented below; they are studied individually further in the chapter.

Packages list:    Consists of a list with the names of all packages (and libraries of origin) needed in the design. Package standard, from the std library, is an exception because it is always visible by default. Examples of very commonly used packages are included in figure 6.1.

Entity declaration:    Describes how the design entity (VHDL code) interfaces with the external environment. For that purpose, all circuit ports are named and specified. A port can communicate, for example, with a design located in another device, in which case the port uses device pin(s). The entity declaration allows also the inclusion of generic constants (among other generic items).

Architecture body:    Contains the VHDL code proper, which describes the circuit behavior or structure, from which a compliant hardware can then be inferred by the compiler. The architecture body is the internal description of the entity declaration.

Package declaration:    This portion of a package contains only declarations, which can be declarations of subprograms, types, subtypes, signals, constants, components, aliases, and several other items. If it contains a declaration of subprogram or deferred constant (a constant with unspecified value), the package body is required.

Package body:    Required in the cases mentioned above. It must contain the corresponding subprogram body and/or the constant’s value or its value definition mechanism (via a function, for example).

Two examples of synthesizable VHDL code are shown in figure 6.2 (do not worry about language details for now), both implementing the add-and-compare circuit of figure 6.2a. The first code (figure 6.2b) employs only data types and operators from the package called standard, from the std library; since this package is visible by default and is the only package needed in this design, the packages list does not need to be typed in. The second code (figure 6.2c) uses standard-logic and unsigned types, defined in the packages std_logic_1164 and numeric_std, respectively (both from the ieee library), so those packages must be included in the packages list. Note that the entity, called add_compare_cell in both cases, contains only the circuit ports, which are indispensable for synthesis. The architecture, called dataflow in both cases, seems more complex in the second code; however, as will be seen later (section 10.7), the second code is indeed recommended.

Figure 6.2
Add-and-compare circuit and corresponding VHDL codes using (b) standard or (c) standard-logic plus unsigned types.

6.2    Libraries and Packages

The main official VHDL packages are listed in table 6.1, along with their typical usage. They are distributed in two libraries called std and ieee. Brief comments on each package follow.

Table 6.1

VHDL libraries and packages

Library

Package

Typical usage

std

1

standard

For implementing all sorts of logic and integer-based arithmetic circuits but with major limitations; not recommended for arithmetic circuits and circuit ports

2

textio

For dealing with text and files

3

env

For communication with the simulation environment

ieee

4

std_logic_1164

For implementing any logic or arithmetic circuit (for the latter, must associate with another package, like #7, #10, or #13)

5

std_logic_textio

Complement to package #2

6

numeric_bit

For implementing integer arithmetic circuits with type unsigned or signed; has bit as base type

7

numeric_std

Same as above but with std_ulogic as base type

8

numeric_bit_unsigned

For doing unsigned operations with type bit_vector

9

numeric_std_unsigned

Same as above for type std_ulogic_vector

10

fixed_pkg

For implementing fixed-point arithmetic circuits

11

fixed_generic_pkg

12

fixed_float_types

13

float_pkg

For implementing floating-point arithmetic circuits

14

float_generic_pkg

15

math_real

For determining generic parameters (support not required for synthesis, but might exist for real values that are static)

Note:    Unofficial packages (std_logic_arith, std_logic_unsigned, std_logic_signed, and so on) should no longer be used, so they will not appear in this edition of the book.

Packages from the std library

Package standard: Defines the standard types bit, bit_vector, integer, boolean, character, and so on and a number of related operators plus other functions.

Package textio: For dealing with text and files.

Package env: For communication with the simulation environment.

Packages from the ieee library

Package std_logic_1164: A very important package, which defines the nine-value type std_ulogic, along with its array version, the std_ulogic_vector type. It also includes the related subtypes std_logic and std_logic_vector, respectively. Its main feature, compared to the original type bit, is the existence of the additional synthesizable values don’t care ('−') and high-impedance ('Z') (type bit only allows '0' and '1').

Note:    We will often refer collectively to the types and subtypes above simply as standard-logic types.

Package std_logic_textio: A complement to the standard textio package.

Package numeric_bit: Introduces the types signed and unsigned and corresponding operators with bit as base type.

Package numeric_std: Same as above but with std_ulogic as base type. As std_logic is a subtype of std_ulogic, the former inherits all operators and other functions defined for the latter.

Package numeric_bit_unsigned: Contains operators and other functions for type bit_vector.

Package numeric_std_unsigned: Contains operators and other functions for type std_ulogic_vector.

Package fixed_generic_pkg: Defines the unsigned and signed fixed-point types ufixed and sfixed, respectively, plus related operators and other functions. It has two associated packages (table 6.1).

Package float_generic_pkg: Defines the floating-point type float (similar to IEEE 754 but with freedom to chose the sizes of the exponent and fraction fields), plus related operators and other functions. It has two associated packages (table 6.1).

To illustrate how a package looks like, below is a small portion of the official package std_logic_1164. The first part, as expected, contains only declarations; the first four introduce the four standard-logic types/subtypes, while the other two are two versions of the and operator (L and R are the left and right operands). Note that the operators are implemented by functions, so a package body is required because it is there that subprogram bodies are constructed (in the case of packages). The function shown in the package body is that that implements the first of the two and operators shown in the declarations. (Details on the construction of packages will be seen in sections 14.1 and 14.2.)

6.3    Packages List in the Code

As seen in figure 6.1, a VHDL code starts with a list of all packages needed to process the design. To make a package visible to the compiler, a library clause plus a use clause pointing to the specific package of that library are needed. This can be done as shown below, where the keyword all causes all elements of that package to be visible. Though not used routinely, it is also possible to point to just a particular member of a package.

The clauses that are introduced automatically (default) are the following:

The example below starts by pointing to the ieee library, which is the library to which the packages std_logic_1164 and numeric_std (pointed to by the two subsequent use clauses) belong (see table 6.1). The last two use clauses point to two user-constructed packages called functions_pkg and procedures_pkg; these are located in the work library, which is the directory were the project files are saved. Note that only one element (a procedure named sort) of procedures_pkg is made visible. Note also that the library work did not need to be listed because it was included already in the default library clause seen above.

The libraries/packages used most frequently are:

Package standard from the std library (visible by default);

Library work (project directory; synthesis and simulation files are visible automatically, packages are not);

Package std_logic_1164, from the ieee library;

Package numeric_std, also from the ieee library.

6.4    Entity Declaration

The syntax below shows the main parts of an entity declaration (or entity, for short), which are the generic and port clauses (together, they form the entity header). The former is optional, while the latter is mandatory for synthesis (empty entities occur in testbenches, which are for simulation, studied in chapter 18).

Entity name:    The syntax above starts with the entity name. For that, any word that obeys the criteria for VHDL identifiers described in section 5.7.6 can be used. However, because some names are better than others, some suggestions are presented in section 5.8.1. Remember that the entity name should be also the project name, so that is another reason for choosing that name carefully.

Generic list:    The first region in the entity declaration above is the (optional) generic list. It is used for declaring global constants (plus other items, described in section 6.7), so parameterized codes can be easily written, which is important for code maintenance, readability, and reusability. Such constants can be used immediately in the next part of the entity, which is the port list. See suggestions in section 5.8.3 regarding the names for these constants.

Port list:    The second region in the syntax above is the port list, which is just a list with specifications of all circuit inputs and outputs. The first specification for a port is a name for it, and suggestions for these are presented in section 5.8.4. The second specification is the port mode, which can be in, out, or inout (after VHDL-2008, mode buffer finally became unnecessary). The final specification is the port type, which can be any of the predefined VHDL types (bit, integer, std_logic_vector, etc.) or a user-defined type (data types are studied in chapters 7 and 8).

Details regarding the port modes are presented in figure 6.3. In (a), the modes are highlighted, showing that in and out are unidirectional, while inout is bidirectional (so the same wires are used for sending and receiving data). In (b), a practical example is shown, in which our circuit (in the FPGA) communicates with an SRAM chip. Note that addr (address) and we (write enable) are of mode out, while data is of mode inout (memory interface circuits are the main users of bidirectional ports). Finally, (c) shows how bidirectional ports are built. The internal transmitting (Tx) and receiving (Rx) paths are connected together via a tri-state buffer (the other buffer, at Rx, is just a regular buffer, inserted whenever a signal is connected to an FPGA pin). For transmission, ena is asserted, allowing data to be sent out (obviously, it is also received locally at Rx, but it is ignored). During reception, ena is kept low, so the tri-state buffer stays in the high-impedance state, avoiding conflict (short-circuit) between the output of the Tx buffer and the received signal.

Figure 6.3
(a) Port modes (in, out, inout); (b) Practical example (SRAM interface); (c) How inout ports are built.

An example of entity declaration, using the SRAM interface circuit of figure 6.3b as reference, is presented next, where it is assumed that the SRAM can store 2¹⁶ bytes of data. The names of the generic constants are ADDR_WIDTH and DATA_WIDTH, both of type natural, with default values 16 and 8, respectively. The ports are named in accordance with figure 6.3b, and their types are all standard-logic types.

Other entity regions:    Though not used frequently, an entity declaration contains two additional parts, the first being a declarative region and the second a statement region, as shown below.

The declarative part can contain the same kinds of declarations and specifications allowed in the declarative part of the architecture (seen in the next section), except for component declarations and configuration specifications. The statement part can contain concurrent assertion statements and passive procedure calls (these do not involve signal assignments and are used, for example, for testing port values in simulation).

In the example below, both of the new regions are used. In the declarative part, a function (named find_max) is constructed, while in the statement part, an assertion statement plus a passive procedure call are shown.

6.5    Architecture Body

The architecture body (or architecture, for short) contains the VHDL code proper, which describes the circuit behavior or structure, from which a compliant hardware can then be inferred by the compiler. It can also contain instances of previous designs. It is composed of two parts, the first being a declarative region and the second a statement region, as shown below.

Architecture name:    The first item in the syntax above is the architecture name. For that, any word that obeys the criteria for VHDL identifiers (section 5.7.6) can be used. Related suggestions are presented in section 5.8.2.

Declarative part:    This region, located before the keyword begin, can contain declarations of subprogram (with subprogram body), package (with package body, if needed), type, subtype, constant, signal, shared variable, file, alias, component, attribute, and group. It can also contain configuration and disconnection specifications plus use clauses. The most popular declarations are of types, signals, and constants.

Statement part:    This region is where the VHDL statements (the code proper) are placed. In other words, here is where the circuits are built. Consequently, most of the material in the coming chapters is related to this region. Some initial examples of architecture bodies can be seen in figures 6.2b and c.

6.6    Object Classes

Another fundamental topic that must be known before entering the study of data types (in the next chapter) is VHDL objects, because it is for them that the types are intended. Such objects are introduced in this section, where they are defined and briefly described, which suffices for the needs in the next few chapters. A detailed study of their behavior, differences, and usage will be presented later in chapter 12 and practiced with in chapter 13.

An object is a named item that has a value of a type. There are four object classes in VHDL: constant, signal, variable, and file. An example is the set of circuit ports, which are all signals. Another example is presented in figure 6.4, which shows an architecture body that contains a process. Note that a constant and a signal declaration are made in the declarative part of the architecture, while a variable declaration is made in the declarative part of the process.

Figure 6.4
Examples of constant, signal, and variable declarations (the first two are in the declarative part of an architecture; the last, in the declarative part of a process).

All four object classes are introduced next. For simplicity, class names are used indistinctly to refer to a class or to an element of a class.

6.6.1    Constant

Members of the constant class have a static value. A constant can be declared (created) in the declarative part of entity, architecture, package, block, generate, process, or subprogram. Its syntax is shown below.

The name of a constant can be any word that obeys the criteria for VHDL identifiers described in section 5.7.6; related suggestions are presented in section 5.8.3. The type of a constant can be any valid VHDL type—either predefined or user defined. Finally, the value of a constant can range from a simple static value up to multiple static values and expressions involving other constants.

The use of constants is important for several reasons. First, it makes the code more readable because, contrary to numeric values, a constant has a (meaningful) name. Second, it avoids the use of numeric values or related expressions inside the code, which can be messy. Finally, it reduces the effort and, more importantly, prevents errors when the value of a constant must be modified, which is particularly critical when that value appears more than once in the code.

A constant declared using the syntax above is said to be an explicitly declared constant. A constant can also be declared without a value, as shown below; then it is called a deferred constant.

A deferred constant is important when its value depends on the application. In this case, it can be declared in a package, with its value then defined in the package body using, for example, a function call with parameters passed by the application. The constant’s value can be omitted also when a constant is in a generic list of a design that is instantiated as a component in another design, so its value is defined at instantiation time (we will see this in chapter 10).

Examples of constant declarations are shown below. Observe that the symbol used to assign a value to a constant is “:=”.

Other examples, employing aggregates and concatenation (section 7.9), are shown next.

6.6.2    Signal

Members of the signal class are capable of passing values in and out of the circuit as well as between its internal parts. In other words, signals represent wires. As already mentioned, all elements of the port list of an entity declaration belong to this class.

Signal declarations can be made in the declarative part of entity, architecture, package, block, and generate. Signals cannot be declared in sequential code (i.e., process and subprograms), though their values can be modified there. Its syntax is shown below.

The name of a signal can be any word that obeys the criteria for VHDL identifiers described in section 5.7.6; related suggestions are presented in section 5.8.4. The type of a signal can be any valid VHDL type—either predefined or user defined. The default value is optional; if not included, an implicit value is assumed, which is the type’s leftmost value—for example, for integers it is the range’s left bound, and for enumeration types it is the first element in the list.

Even though default values for signals might be useful for simulation, that is hardly so for synthesis, because physical initialization is rarely possible. For example, say that we want to implement a circuit that computes sum = a + b; how can we give a default value to sum if there is no memory and the value of sum depends entirely on a and b? (The default value would be ignored by the compiler.) Besides being often inappropriate, another problem with explicit default values for signals is that they can send a wrong message to an unaware reader, who might believe that that indeed occurs. In summary, default values for signals should be avoided in code that is for synthesis.

An apparent exception regarding initial values is the case of flip-flops in FPGAs, which are usually reset to zero automatically upon power up, whether they have a default value or not. An actual exception, however, does exist for some FPGAs, which allow the flip-flops to be initialized to '1', but that is just for flip-flops and for some FPGAs (and not for ASICs either).

Examples of signal declarations, followed by signal assignments, are shown next. Note that the symbol to assign a value to a signal is “<=” (for constants, variables, and default/initial values it is “:=”). Many other details about signals (and variables) will be seen in section 12.9.

6.6.3    Variable

As will be seen in section 12.9, variables are a major feature of VHDL. Contrary to signals, they do not represent wires (they can only do that indirectly, via signals). They are used solely in sequential code, where they are updated immediately, thus recommended for implementing particularly circuits that involve counters and loops.

A variable can be declared (created) only in the declarative region of a process or subprogram. Its syntax is shown below.

The name of a variable can be any word that obeys the criteria for VHDL identifiers described in section 5.7.6; related suggestions are presented in section 5.8.4. The type of a variable can be any valid VHDL type—either predefined or user defined. The initial value is optional and the same comments presented for signals (section 6.6.2) apply here; however, because variables are always internal to sequential code, explicit initial values can be helpful in some cases, like the implementation of loops in subprograms (see the variables in examples 14.2 and 14.5).

A variable declaration plus a corresponding variable assignment are shown below. Note that the symbol to assign a value to a variable is “:=”.

Shared Variable:    This is a subclass of the variable class, consisting of variables that can be accessed by multiple processes. Its declaration syntax is shown below (note the absence of initial value):

A shared variable can be declared in the declarative region of package (with restrictions), entity, architecture, block, and generate. Its type has to be a protected type.

Shared variables are intended for simulation. Though some compilers offer some synthesis support, they should be avoided because they are often a source of confusion, and anyway they do not add any relevant help to the construction of codes for synthesis.

6.6.4    File

The last object class is file. However, this is not for synthesis but for simulation, so it will be seen in chapter 18, which concentrates all that is specific for simulation.

6.7    Generics

The use of generics was introduced in section 6.4. It allows the construction of parameterized codes, improving code organization, readability, and reusability. Moreover, because the entity declaration is the visible part (the interface) of a design, and generics are part of entity declarations, it becomes clear that that design is configurable.

In principle, a generic list should contain only the parameters that need to be modified for the circuit to have any desired size. For example, the circuit of figure 6.2 only needs one constant in the generic list, which is the number of bits in the inputs, so the resulting entity declaration could be the following:

The options available after VHDL-2008 are listed below.

What can go in a generic list: constants, types, packages, and subprograms.

Where can a generic list go: in entities (and components, therefore), packages, and subprograms.

Be aware, however, that synthesis support for the new options is still limited. For constants, which were the only generics before VHDL-2008, there are no restrictions, except possibly for the inclusion of expressions (explained shortly).

The list on the left below summarizes what a generic list can contain, while the list on the right shows partial syntax details with particular attention to constant and type.

Note above that a constant can be declared without a value, which is assigned later by a generic map association (section 10.5) when the design containing this generic list is instantiated in another design. Regarding the type, it is always left unspecified (only an identifier for it is defined), the missing details being defined later (sections 14.2 and 14.6). The next example shows a generic list with a type and two constants, the last one being a deferred constant.

Using dependent constants in generic lists    An important feature introduced in VHDL-2008 is that generic constants can employ expressions involving other generic constants, as in the examples below.

Note, however, that the final constant in all three examples above is not independent, so it is not meant to ever be modified by the user. On the other hand, these dependent constants help make the code that follows less verbose and more readable. For instance, the second of the examples above is particularly important because the number of bits required to represent an integer is needed frequently (see details on this declaration in section 7.6.6).

In case the compiler does not yet support expressions in generics, but one wants a dependent constant to be in the generic list (for the reasons mentioned above), a temporary alternative for the exercises (proposed later) is to calculate the constant’s value “manually” and include a clarifying comment in the code, as illustrated below.

Additional details on generics will be seen in sections 14.2 and 14.6 during the study of packages and subprograms.

6.8    Entity-Architecture Binding

During development or simulation, one might want to write several experimental architectures for the same entity. Since only one entity-architecture pair can be processed at a time, it is necessary to inform which architecture should be picked. For that, the configuration declaration below can be used.

The first item in the syntax above is the configuration name, which can be any word that obeys the criteria for identifiers described in section 5.7.6. Next comes the name of the entity to be compiled, followed by the name of the selected architecture. A configuration declaration must be located outside any entity or architecture.

An example is shown in figure 6.5 for an entity called test, with two architectures, called arch1 and arch2. Note that the configuration declaration, named config, is located outside the entity and architectures and in this case indicates that the pair test-arch1 should be used in the compilation.

Figure 6.5
Entity-architecture biding with a configuration declaration.

In the case of component instantiations (studied later, in section 10.5), the architecture can be selected directly in the instantiation as follows:

If the design of figure 6.5 were instantiated in another design, the following could then be used:

We will return to this in chapter 10.

6.9    Introductory VHDL Examples

We close this chapter by presenting three introductory examples of synthesizable VHDL code. Though we have not studied the language yet, these examples help illustrate fundamental aspects concerning code structure, which is the main focus of this chapter.

Example 6.1. Parity detector

This first example shows the implementation of a combinational circuit; it is the tree-type (as opposed to chain-type) parity detector of figure 1.5b, here with eight bits at the input. The input and output are called byte and parity, respectively. A VHDL code for it is shown below; several comments follow.

Lines 1, 4, 10, and 16: These dashed or blank lines, which are obviously optional, were used to delimit or separate the code sections according to figure 6.1, making the code clearer.

Lines 2–3: Packages list that consists of a library clause followed by a package use clause. Because standard-logic types are employed, the corresponding package, std_logic_1164, from the ieee library, must be in this list.

Lines 5–9: Entity declaration, called parity_detector in this example.

Line 7: Input port (mode is in), of type std_logic_vector.

Line 8: Output port (mode is out), of type std_logic.

Lines 11–15: Architecture body, called tree_type in this example.

Lines 13–14: Statement part of the architecture. In this case, only the xor operator is needed. The arrangement of the parentheses guarantees that the tree-type structure (figure 1.5b) is inferred.

Line 13: Note that “<=” is used to assign a value to a signal (circuit ports are always signals). In contrast, “:=” would be used for a variable, constant, or default/initial value.

Figures 6.6a and b show the results produced by the compiler. The RTL view is shown in (a), indicating that the circuit is functionally correct, but information about the actual physical implementation is not provided by the RTL view. For that, the view after placement/fitting is needed, and this is presented in (b), confirming the tree-type arrangement.

Figure 6.6
(a) RTL and (b) post-placement views relative to the parity detector of example 6.1; (c) Timing simulation results.

Results from timing simulation are depicted in figure 6.6c, which the reader is invited to examine. Note the distance between signal transitions (80ns), in accordance with appendix D (topic 1c). A glitch can also be observed in the graph, which is normal, as also explained in appendix D (topic 2).

Note:    It will be shown in chapter 9 that VHDL-2008 introduced the reduction xor operator, so the implementation above can be done using simply parity <= xor byte; (though without guarantee about the tree-type structure).

Example 6.2. D-type flip-flop (DFF)

Flip-flops were reviewed in section 2.3. Say that we want the compiler to build a circuit containing just a single DFF, like that in figure 2.3a. A classical approach is presented below. One must remember that VHDL code is inherently concurrent, so to implement a sequential circuit we must “force” VHDL to be sequential, which is done by placing the code inside a process (lines 13–20). Other comments are presented after the code.

Lines 1, 4, 10, and 22: These dashed or blank lines, which are obviously optional, were used to delimit or separate the code sections according with figure 6.1, making the code clearer.

Lines 2–3: See comments in example 6.1.

Lines 5–9: Entity declaration, called flip_flop in this example.

Line 7: Input ports (mode is in), all of type std_logic.

Line 8: Output port (mode is out), also of type std_logic.

Lines 11–21: Architecture body, here with the same name as the entity’s.

Lines 13–20: Statement part of the architecture. In this case, the code just contains a process that is needed because we want to implement a sequential circuit (a 1-DFF register should be inferred).

Line 13: Note that two signals (clk, rst) are included in the process’s sensitivity list (the process executes when any of these signals changes).

Lines 15–16: If rst is raised, the flip-flop is reset, regardless of clk (so this is an asynchronous reset).

Lines 17–18: If rst is low and a positive clock transition occurs, the input (d) is copied to the output (q). Since the output must remain unchanged between (positive) clock edges and a DFF is precisely a circuit that does that, the compiler will understand that a DFF is wanted and will therefore infer that.

Lines 16 and 18: Note that the symbol “<=” is used to assign a value to a signal.

Example 6.3. Registered add-and-compare circuit

Figure 6.7a shows the same circuit of figure 6.2a but now with the outputs registered (i.e., stored in DFFs). A corresponding VHDL code is presented next. The combinational part (adder plus comparator) could be implemented using concurrent code before entering the sequential unit (process), but for simple, well-delimited circuits, using the process as a “module” that encapsulates the entire circuit is usually a good idea. Other comments are presented after the code.

Figure 6.7
(a) Registered add-and-compare circuit of example 6.3; (b) RTL view produced by the compiler.

Lines 1, 5, 15, etc.: See comments in example 6.1.

Lines 2–4: Packages list. Because standard-logic and unsigned types are used and because they are defined in the packages std_logic_1164 and numeric_std (both from the ieee library), respectively, those packages must be in the list.

Lines 6–14: Entity declaration, named add_compare_registered (see naming guidelines in section 5.8). In this example, it contains also a generic list to attain a parameterized code.

Lines 7–8: Generic list, with a single constant named NUM_BITS of type natural and default value 8.

Lines 10–13: Circuit ports, clk, a, and b being inputs, and comp plus sum outputs.

Lines 16–35: Architecture body, named rtl (see naming guidelines in section 5.8).

Line 17: Declarative part of the architecture, containing two auxiliary signals to help in the std_logic_vector to unsigned conversion.

Lines 20–21: Extension of a and b and conversion from std_logic_vector to unsigned (section 10.7). The extension (of at least one of the inputs, though both were extended here) is needed because the “+” operator (line 31) for unsigned requires that the output be of the same width as the largest input.

Lines 23–33: A process that, at the rising clock edge, stores the comparison and sum results in DFF registers, producing the desired outputs.

Line 23: Note that clk is the only signal in the process’s sensitivity list (no reset in this example).

The RTL view produced by the compiler after processing the code above is shown in figure 6.7b, which is in full agreement with figure 6.7a. The reader is invited to synthesize and simulate this code (and the previous two as well), thus practicing with the tutorials of appendices A to C.

7    Predefined Data Types

VHDL is said to be a strongly typed language because of several features: it does not allow data transfer between objects of different types (even if the type declarations, except for the identifiers, are exactly the same); it does not allow implicit type conversion; and every operator is constructed for specific data types. The subject of data types in VHDL, including both predefined and user-defined types, is relatively complex, but it is indispensable to understand it well to write code efficiently. Predefined types are described in this chapter, while user-defined types are treated in chapter 8. Corresponding operators and attributes are described subsequently in chapter 9.

7.1    Predefined VHDL Types

The types defined in the package standard are considered the true predefined types of VHDL because they are built into the language. However, we saw in chapter 6 that, by including the right package in the packages list of a VHDL code, any type can behave as if it were also predefined. It is to this broader view, which includes all official types, that the title of this chapter refers to. In summary, we consider as predefined all official VHDL types, the main of which, for synthesis, are listed in table 7.1.

Table 7.1

Main synthesizable predefined types (with informal name abbreviations)

Notice that table 7.1 also establishes some informal type name abbreviations (in the fourth column) and type group designations (in the fifth column), which will help simplify the text and exercises that follow.

A final important information before we start is the typical usage of the predefined types, which is summarized below.

Standard types (created in the package standard): for general internal use, though no longer frequent, except for boolean and integer (but the latter is not recommended to do arithmetic).

Standard-logic types (created in the package std_logic_1164): recommended for implementing logic circuits and for representing the ports of any (logic or arithmetic) circuit whenever possible.

Unsigned and signed types (created in the package numeric_std): recommended for implementing arithmetic circuits that operate with integers.

Fixed-point types (created in the package fixed_generic_pkg): recommended for implementing arithmetic circuits that operate with fixed-point data.

Floating-point types (created in the package float_generic_pkg): recommended for implementing arithmetic circuits that operate with floating-point data.

7.2    Type Classes

There are five official type classes in VHDL, called scalar, composite, file, access, and protected types. They are detailed in table 7.2, where the first two classes are divided in four and two subclasses, respectively. The last column shows the types and subtypes that are used most frequently, among which there are two types, called time and real, that are intended for simulation (though some synthesis support for the latter normally exists, limited to static values). The last three classes are also intended for simulation.

Table 7.2

VHDL types classification in the IEEE 1076 standard

Type class

Subclass

Main predefined types (and subtypes) in each class

Scalar types

Integer types

integer (natural, positive)

Enumeration types

bit

boolean

character

std_ulogic (std_logic)

Float. point types

real

Physical types

time

Composite types

Array types

bit_vector

boolean_vector

integer_vector

real_vector

time_vector

string

std_ulogic_vector (std_logic_vector)

unsigned

signed

ufixed

sfixed

float

Record types

Collection of named elements of same or different types

Access types

Used for creating pointers in simulation models that change dynamically or with structures that are not known in advance. (Pointers should be avoided as much as possible.)

File types

For dealing with files (opening, reading, etc.) during simulation.

Protected types

To prevent multiple processes from simultaneously accessing the same shared variable during simulation. (Shared variables should be avoided as much as possible.)

7.3    Type Declarations

A scalar type is declared using the keyword type, as follows:

The name of a type can be any word that obeys the criteria for VHDL identifiers described in section 5.7.6. In the examples below, the official declarations for the first three scalar types listed in the last column of table 7.2 are shown, the first being an integer type and the others enumeration types (i.e., lists of “values”).

An array type is declared using the keywords type and array, as follows:

The examples below show the official declarations for the first three array types listed in the last column of table 7.2.

The “natural range <>” specification (the symbol <> is called a box) informs that the range is unconstrained with the only limitation that it must fall within the natural range (0 to 2³¹ − 1). The example below shows a totally unconstrained type (std_ulogic_vector), a partially constrained type (integer_array), and a fully constrained type (bit_array). The missing constraints must be provided when the type is used, as shown in the three subsequent signal declarations.

The simple fact that two types have different names suffices for them to be considered different types, even if everything else in their declarations is equal. That is the case in the first two lines of the example below for types std_ulogic_vector (from the std_logic_1164 package) and std_ulogic_array (user defined), so values can not be passed between them directly. However, if a single element of each is taken, the transfer is legal because then we are dealing with the same type, std_ulogic, which is the base type of both. Note also that while the former has support for predefined operators (like the logical operators not, and, nand, etc.), nothing is available for the latter, which has just been created.

There are some exceptions, however, with respect to the operators for built-in types (i.e., from package standard). Any user-defined type that is based on integer, on a vector of bits, or on a vector of booleans inherits all properties (operators and other functions) available for the predefined type integer, bit_vector, or boolean_vector, respectively (though they are still considered to be different types). This is illustrated below for vectors of bits and for integers.

7.4    Subtypes

A subtype is a type with some constraint. It can be declared as follows:

Several of the types studied in this chapter have subtypes, like the type integer whose subtypes are natural and positive, as shown below. In this case, the constraint is related to the range.

Additional examples of official types with subtypes are shown next, where std_logic is a subtype of std_ulogic, and std_logic_vector is a subtype of std_ulogic_vector. In this case, instead of range constraints, a resolution function (described in section 7.6.2) is employed in the subtypes.

The advantages of declaring a subtype instead of a new type are that values can be passed directly between a subtype and its parent type and that a subtype inherits all properties (operators support, for example) of its parent type (none of these properties hold between two different types, except for the cases described in section 7.3). Such properties are due to the fact that a type is considered a subtype of itself. The use of subtypes is illustrated in the example below.

7.5    A Note on Operators and Attributes

VHDL provides a rich set of operators for the predefined data types. The main ones are:

Logical operators: not, and, nand, or, nor, xor, xnor

Arithmetic operators: +, −, *, /, **, abs, mod, rem

Comparison operators: =, /=, >, <, >=, <=, ?=, ?/=, ?<, ?>, ?>=, ?<=

However, none of the predefined data types have support for all of them; for instance, bit_vector allows logical and comparison operations but does not support arithmetic operations. Consequently, to use the predefined types efficiently, it is indispensable to know what kind of support they have. Even though we are already using some operators in the examples, a detailed study of VHDL operators will be presented in chapter 9.

VHDL also provides helpful attributes, and these are fine for user-defined types too. A particularly useful attribute is one that extracts information about the range of array type objects. As an example, consider the signal below, of type std_logic_vector (which we know is an array type):

The following would result upon application of some of the array attributes: s'left=7, s'right=0, s'high=7, s'low=0, s'length=8, s'range=(7 downto 0), and so on.

Like VHD operators, VHDL attributes too will be described in detail in chapter 9.

7.6    Study of Predefined Data Types

Each of the synthesizable predefined types (the main ones are listed in table 7.1) is described individually in this section.

7.6.1    Standard Types

These are the types declared in the package standard, summarized in table 7.3. The actual declarations for those that are synthesizable are presented next. In the examples, signal (section 6.6.2) and variable (section 6.6.3) declarations are presented, followed by corresponding signal and variable assignments (recall that the assignment symbol for signals is “<=”, while for variables it is “:=”).

Table 7.3

Types and subtypes declared in the package standard

Type bit

Definition: type bit is ('0', '1');

This is a scalar enumeration type (table 7.2), with only two values, written in single quotes.

Example:

Type bit_vector

Definition: type bit_vector is array (natural range <>) of bit;

This is a composite array type (table 7.2), consisting of an unconstrained number of bit elements, with the only limitation that the range must fall within the natural range. Recall that multi-bit values are written in double quotes.

Example:

Type boolean

Definition: type boolean is (false, true);

This is another scalar enumeration type (table 7.2), with only two values.

Example:

Type boolean_vector

Definition: type boolean_vector is array (natural range <>) of boolean;

This is another composite array type (table 7.2), with an unconstrained number of boolean elements.

Example:

Type integer

Definition: type integer is range -2147483648 to 2147483647;

This type defines 32-bit (default) signed integers, hence ranging from 2³¹ to 2³¹−1. For non-negative integers, the use of subtype natural (or even positive) is recommended (it is more informative and less error prone).

Example:

Subtype natural

Definition: subtype natural is integer range 0 to integer'high;

This is a subtype of integer, so it inherits all of its properties (operators and other functions). Its range is from 0 to 2³¹−1.

Example:

Subtype positive

Definition: subtype positive is integer range 1 to integer'high;

This is another subtype of integer. Its range is from 1 to 2³¹−1.

Example:

Type integer_vector

Definition: type integer_vector is array (natural range <>) of integer;

It is another composite array type (table 7.2), consisting of an unconstrained set of integers.

Example:

Type character

Definition: type character is (nul, soh, …, 'a', 'b', 'c', …, '0', '1', '2', …);

This is another scalar enumeration type (table 7.2). It consists of 256 values, from the ISO 8859-1 standard. Like a single bit, a single character is written in single quotes.

Example:

Type string

Definition: type string is array (positive range <>) of character;

This is another composite array type (table 7.2). Like a multi-bit value, a multicharacter value (a string) is written in double quotes.

Example:

7.6.2    Standard-Logic Types

These are the types defined in the package std_logic_1164, summarized in table 7.4.They operate with a total of nine logic values, where '0', '1', 'L', and 'H' are called logical values, 'U', 'X', 'W', and '−' are called metalogical values, and 'Z' is called high-impedance value. 'Z' represents the high-impedance output state of tri-state buffers (figure 1.1), while '−' represents “don’t care” values, important for boolean equations simplification.

Table 7.4

Types and subtypes defined in the package std_logic_1164

Recall that type bit (table 7.3), specified in the first version of VHDL, exhibits only values '0' and '1', but four logic values are needed in synthesis, namely '0', '1', and '−' for inputs and outputs, plus 'Z', for outputs only. This discrepancy was corrected with the introduction of package std_logic_1164 at the time of VHDL-1993 (updated in VHDL-2008), with the nine values listed in table 7.4. However, because for synthesis the four values just mentioned suffice, the following is usually adopted by compilers during synthesis: 'L'='0', 'H'='1', 'X'='−', and 'Z'. As a good design practice, values other than '0', '1', 'Z', and '−' should not be used.

The standard-logic types are the most commonly used types in the industry, so they were selected to represent the circuit ports in nearly all examples (and exercises) in this book. Brief descriptions for them follow.

Type std_ulogic

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

This is a scalar enumeration type (table 7.2), with a total of nine values.

Example:

Subtype std_logic

Definition: subtype std_logic is resolved std_ulogic;

Being a subtype of std_ulogic, it inherits all of its operators and other functions.

Example:

The subtype std_logic is said to be a resolved version of the type std_ulogic because it is subject to the resolution table below, which determines the resulting value when a signal is driven by multiple sources. For example, if the values '0' and '1' are assigned to a signal in two different processes, an error would be detected when using std_ulogic, while the value 'X' (check table below) would be assigned if using std_logic. However, for synthesis, the use of multiple drivers for the same signal is automatically verified by the compiler (short-circuits would result between values '0' and '1'), disabling compilation in case that occurs; consequently, the use of std_logic for synthesis is safe.

Type std_ulogic_vector

Definition: type std_ulogic_vector is array (natural range <>) of std_ulogic;

This is another composite array type (table 7.2), consisting of an unconstrained collection of std_ulogic elements.

Example:

Subtype std_logic_vector

Definition: subtype std_logic_vector is (resolved) std_ulogic_vector;

Being a subtype of std_ulogic_vector, it inherits all of its operators and other functions.

Example:

7.6.3    Unsigned and Signed Types

These types, having std_ulogic as base type, are defined in the package numeric_std. They are summarized in table 7.5, and their actual declarations are presented next, followed by examples. Since these types are intended for arithmetic circuits, only logic values '0' and '1' should be used (indeed, most operators for SU-based types, like SUV, SLV, UNS, SIG, UFIX, SFIX, and FLO, employ translation functions, mainly the To_01, described in section 7.10.4). Additionally, because in VHDL the leftmost bit is always the most significant bit (MSB), the use of descending index ending in zero is also recommended.

Table 7.5

Types and subtypes defined in the package numeric_std

Type or Subtype

Values

T

unresolved_unsigned

Unsigned representation for integers using string of std_ulogic elements. Use subtype unsigned below for designs.

S

unsigned

Resolved unsigned subtype. Recommended for unsigned integer arithmetic circuits (use only '0's and '1's).

T

unresolved_signed

Signed representation for integers using string of std_ulogic elements. Use subtype signed below for designs.

S

signed

Resolved signed subtype. Recommended for signed integer arithmetic circuits (use only '0's and '1's).

Type unresolved_unsigned

Definition: type unresolved_unsigned is array (natural range <>) of std_ulogic;

This is another composite array type (table 7.2), consisting of a collection of std_ulogic elements. Its subtype (unsigned, shown below) is usually employed.

Subtype unsigned

Definition: subtype unsigned is (resolved) unresolved_unsigned;

This subtype is a resolved version (see comments in section 7.6.2) of the type above. It is the recommended alternative for implementing unsigned integer arithmetic circuits (section 10.7).

Example:

Type unresolved_signed

Definition: type unresolved_signed is array (natural range <>) of std_ulogic;

This is the signed counterpart of the unresolved_unsigned type above. Its subtype (signed, shown below) is usually employed.

Subtype signed

Definition: subtype signed is (resolved) unresolved_signed;

This subtype is a resolved version of the type above. It is the recommended alternative for implementing signed integer arithmetic circuits (section 10.7).

Example:

7.6.4    Fixed-Point Types

Fixed-point types and subtypes are defined in the package fixed_generic_pkg and are summarized in table 7.6. Since they are intended for arithmetic circuits, only logic values '0' and '1' should be used (see the comment on translation functions at the beginning of section 7.6.3).

Table 7.6

Fixed-point types and subtypes defined in the package fixed_generic_pkg

Type or Subtype

Values

T

unresolved_ufixed

Unsigned representation for fixed-point values using string of std_ulogic elements. Use ufixed subtype below for designs.

S

ufixed

Resolved unsigned fixed-point subtype. Recommended option for unsigned fixed-point arithmetic circuits (use only '0's and '1's).

T

unresolved_sfixed

Signed representation for fixed-point values using string of std_ulogic elements. Use sfixed subtype below for designs.

S

sfixed

Resolved signed fixed-point subtype. Recommended option for signed fixed-point arithmetic circuits (use only '0's and '1's).

The range must always be descending, from L down to R, where L (left) is usually positive and R (right) is usually negative, as illustrated in figure 7.1a. Note that the integer part is formed by the non-negative indexes, while the fraction is formed by the negative indexes. A numeric example is shown in figure 7.1b, where L=5, R=−3, and the corresponding decimal value (assuming unsigned) is 2⁵ − 2¹ − 2⁰ − 2⁻² = 35.25.

Figure 7.1
Representation of fixed-point data.

Note that if we multiply the result above (35.25) by 2⁻R (=8) we obtain 282, which is the decimal value of that vector if we ignore the “point,” so a fixed-point value is just a down scaling of an integer by a power-of-two factor, thus not requiring additional hardware (except for additional features, like rounding or saturation; see also the resize function for fixed-point types in section 7.9.3).

To use the type ufixed or sfixed, the package fixed_generic_pkg must be included in the packages list (figure 6.1). However, if the default parameter values are accepted, a default version of that package, called fixed_pkg, can be listed instead.

The actual definition for each member of table 7.6 is presented next. In the examples, again a signal declaration plus a corresponding signal assignment are shown.

Type unresolved_ufixed

Definition: type unresolved_ufixed is array (integer range <>) of std_ulogic;

This is another array type (table 7.2), consisting of a collection of std_ulogic elements. Its subtype (ufixed, shown below) is usually employed.

Subtype ufixed

Definition: subtype ufixed is (resolved) unresolved_ufixed;

This subtype is a resolved version (see comments in section 7.6.2) of the type above. It is the recommended alternative for implementing unsigned fixed-point arithmetic circuits (section 10.7).

Example:

The signal declaration above has L=3 (hence 4 bits for the integer part of s) and R=−2 (thus 2 bits for the fraction part of s). The value passed to s in the assignment is s = 2³ − 2⁰ − 2⁻² = 9.25, which can also be calculated using s = integer_value/2⁻R = 37/4 = 9.25.

Type unresolved_sfixed

Definition: type unresolved_sfixed is array (integer range <>) of std_ulogic;

This is the signed counterpart of the unsigned type above. Its subtype (sfixed, shown below) is usually employed.

Subtype sfixed

Definition: subtype sfixed is (resolved) unresolved_sfixed;

This subtype is a resolved version of the type above. It is the recommended alternative for implementing signed fixed-point arithmetic circuits (section 10.7).

Example:

The signal declaration above uses again L=3 and R=−2. The value passed to s is s = −(two’s complement of "1001.01") = −"0110.11" = −6.75, which can also be calculated as s = (integer_value − 2BITS)/2⁻R, where BITS (=L−R+1) is the number of bits in s, thus resulting s = (37−2⁶)/2² = −6.75.

Two rounding styles for types ufixed and sfixed are defined in the fixed_generic_pkg package and are called fixed_round_style (default) and fixed_truncate. The former, which is the recommended approach, is similar to the round to nearest with ties rounded to nearest even style, listed in line 6 of tables 1.1 and detailed in table 1.2 (this is also the default style for floating-point values, section 1.6.5). As an example where truncation plus rounding are employed for type ufixed, see the example using the resize function in section 7.9.3.

7.6.5    Floating-Point Types

Floating-point types and subtypes are defined in the package float_generic_pkg and are summarized in table 7.7. Since these types too are intended for arithmetic circuits, only logic values '0' and '1' should be used (see the comment on translation functions at the beginning of section 7.6.3). They follow the IEEE 754 standard closely, so a review of section 1.6.5 is suggested before proceeding.

Table 7.7

Main types and subtypes defined in the package float_generic_pkg

Type or Subtype

Values

T

unresolved_float

Main type, string of std_ulogic values; use subtypes below, with only '0's and '1's because floating-point is intended for arithmetic circuits.

S

float

Generic range.

S

float32

IEEE-754, 32-bit precision, float(8 downto −23).

S

float64

IEEE-754, 64-bit precision, float(11 downto −52).

S

float128

IEEE-754, 128-bit precision, float(15 downto −112).

The range must always be descending, from L down to R, as illustrated in figure 7.2a. As seen in section 1.6.5, the leftmost bit (S) is the sign bit, the next Ewidth bits form the exponent (E), and the final Fwidth bits form the fraction (F). Note that Ewidth = L and Fwidth = −R. (As a side note, the float_generic_pkg package mentions “6 downto −9” as maybe the smallest range for practical use—though much smaller ranges can be employed.)

Figure 7.2
Representation of floating-point data.

The corresponding decimal value (dec) is determined by the equations given in section 1.6.5. For the numeric example of figure 7.2b, where L=5, R=−3, Ewidth=5, Fwidth=3, S=1, E=14, F=0.25, and BIAS=2⁵⁻¹ − 1=15, the result is dec = −(1 + 0.25)¹⁴⁻¹⁵ = −0.625.

To use floating-point types, the package float_generic_pkg must be included in the packages list (figure 6.1). However, if the default parameter values are accepted (the exponent and fraction widths are overwritten by those specified in the design, and the default rounding procedure is that described at the end of this section), a default version of that package, called float_pkg, can be listed instead.

The actual definition for each member of table 7.7 is presented next. In the examples, a signal declaration plus a corresponding signal assignment are again shown.

Type unresolved_float

Definition: type unresolved_float is array (integer range <>) of std_ulogic;

This is another array type consisting of a collection of std_ulogic elements. Its subtypes, listed below, are usually employed.

Subtype float

Definition: subtype float is (resolved) unresolved_float;

This subtype is a resolved version (see comments in section 7.6.2) of the type above. It is the recommended alternative for implementing floating-point arithmetic circuits (section 10.7).

Example:

The signal declaration above uses L=3 (hence 3 bits for the exponent) and R=−4 (thus 4 bits for the fraction). The value passed to s is s = (S=1)(E=010)(F=0110) = −(1 + 0.375)2²⁻³ = −0.6875.

Subtype float32

Definition: subtype float32 is float (8 downto −23);

Example:

The value assigned to s above is s=(S=0)(E=00001111)(F=10 … 0) = (1 + 0.5)2¹⁵⁻¹²⁷ ≈ 2.9●10⁻³⁴.

Subtype float64

Definition: subtype float64 is float (11 downto −52);

Subtype float128

Definition: subtype float128 is float (15 downto −112);

Four rounding styles for type float are defined in the float_generic_pkg package and are called round_nearest (default), round_inf (rounds up), round_neginf (rounds down), and round_zero (truncates). The round_nearest style, which is the default and recommended approach, is the same as the round to nearest with ties rounded to nearest even style listed in table 1.1 and detailed in table 1.2 of section 1.6.4. Several examples are shown there and also in section 1.6.5 (particularly in table 1.4), which is dedicated entirely to reviewing floating-point details and the IEEE-754 standard.

7.6.6    Type real

Even though type real is not intended for synthesis, it usually has some synthesis support for static values, so it can be helpful for defining general circuit parameters.

The functions available in the math_real package are listed in table 7.8. The first group is useful for rounding: ceil rounds up; floor rounds down; round rounds to nearest value (if value is exactly at midway, rounds away from zero); and trunc causes truncation. Function round, for example, can be used to round the division of two constants. In the second group, a particularly helpful function is log2 (log base two), which, together with ceil, can be used to determine the number of bits needed to represent an integer. Both cases are illustrated in the examples below. Obviously, when such functions are used, the math_real package must be included in the code’s packages list. Recall that, because the equations in the examples below are used just for defining circuit parameters, they do not infer hardware (so their complexity is not a concern).

Table 7.8

Functions available in the math_real package

Example 1: Finding the number of bits needed to represent an integer (with math_real)

The second line of code above calculates the number of bits needed to represent the signal in the first line, which is an integer in the 0-to-MAX range. It is given by ⎡log₂(MAX+1)⎤, thus implemented with the log2 and ceil functions. Because these functions operate on type real, type conversions from integer to real and then from real to integer are needed (this kind of type conversion is called type cast, described in section 7.10.2).

Example 2: Rounding the result of a division to the nearest integer (with math_real)

The example above shows the division of two integers, representing the periods of signals clk and clk_pwm, of a PWM circuit (section 2.11.4) to obtain another integer constant, called CLK_DIVIDER. Function round is employed, but because it operates on type real, type cast from integer to real and then from real to integer is again needed.

Example 3: Rounding the result of a division to the nearest integer without external functions

Two options are shown above (for the same constants of example 2). Since the result of a division of integers is simply truncated (therefore, rounded down), the first option returns 70/20=3. However, the second option makes use of the property that rounding a/b to the nearest integer can be obtained using (a − b/2)/b, so it returns 70/20=4.

7.7    Record Types

As seen in table 7.2, VHDL composite types are divided in two subcategories: array types and record types. While the former (seen in many of the cases above) is a single collection of only one element type, the latter can have multiple members, with elements that may be of different types. Records are useful when several objects need to be manipulated together, which is helpful particularly in simulation (e.g., example 18.2).

As seen in section 7.3, to declare an array the keywords type and array are used; for a record, the keywords are type and record. For comparison, both cases are illustrated below.

The boxes below summarize how a record is created and how an individual record element can be accessed.

As shown above, to access an element of a record we write the name of the record-type object, followed by a dot, and then the name of the record element of interest and corresponding constraint. This is illustrated in the example below, which makes use of the memory_access record declared previously.

7.8    Access Types, File Types, and Protected Types

The last three type classes (table 7.2) are access types, file types, and protected types. Access types are used to create pointers in simulation models that change dynamically or with structures that are not known in advance. File types are used for dealing with files (opening, reading, etc.) during simulation. Finally, protected types are used to prevent that multiple processes access the same shared variable during simulation.

In summary, all three are intended for simulation. However, while file types are needed (and are simple to use), the use of pointers and/or shared variables is confusing and error prone, so these should be avoided whenever possible. File types will be described in chapter 18, which concentrates all that is specific for simulation.

7.9    Aggregation, Concatenation, and Resizing

This section shows mechanisms for putting data pieces together to form or enlarge data arrays. It also shows ways to extend or shrink data arrays using the resize function. Such mechanisms are separated in three categories: aggregation, concatenation, and resizing.

7.9.1    Data Aggregation

To build an aggregate, we write the elements to be combined within parentheses, separated by commas, as illustrated below. The keyword others, when used, must come last.

Another example is shown below, this time with integer as base type.

Note in the examples above that only single elements of the base type were aggregated. VHDL-2008 allows also arrays of the base type to be aggregated, so the following assignments would be legal:

The association can be positional or named (mixed associations are not allowed). In the examples above, only the former was employed. Named associations, illustrated next, allow the use of choice expressions, where the following can be used: to (for ascending index direction), downto (descending direction), and “|” (means or). The order of the elements is irrelevant here, except for others, which must come last when used.

The next example shows named associations with integer as base type.

Examples of illegal aggregates are shown below.

Aggregates can also be used on the left side of an assignment, as illustrated below.

Another example with an aggregate on the left is shown below. It implements an adder, with the leftmost bit passed to cout (carry-out bit) and the other bits passed to sum.

7.9.2    Data Concatenation

This is another way of putting separate data pieces together to build or enlarge arrays. The associations are made using the concatenation operator (&), which can join individual elements of the base type as well as arrays of the base type. Contrary to aggregation, in concatenation the use of parentheses is optional (though they can improve readability) and the keyword others is not allowed. Concatenation of aggregates is legal. The examples below show legal and illegal cases.

7.9.3    Resizing Data Arrays

The resize function can be used to extend or reduce the size of arrays. This function is available in several packages, for several data types, as shown in table 7.9. Note, however, that for some types the resize function includes only the unsigned version.

Table 7.9

Options for the resize function

Function

For types/subtypes

Package of origin

resize

unsigned, signed (1)

numeric_bit

unsigned, signed (2)

numeric_std

bit_vector

numeric_bit_unsigned (3)

std_ulogic_vector, std_logic_vector

numeric_std_unsigned (3)

ufixed, sfixed

fixed_generic_pkg

float

float_generic_pkg

(1) Base type is bit. (2) Base type is std_ulogic. (3) Only unsigned resizing.

The operation of the resize function for types unsigned and signed (which represent integers) is illustrated in figure 7.3. Unsigned extension places zeros on the left (most significant positions) of the original vector, while signed extension replicates the MSB (hence keeping the value unchanged). Unsigned reduction eliminates the MSBs of the original vector, while signed reduction eliminates the bits positioned immediately to the right of the MSB (thus preserving the sign).

Figure 7.3
Array extension (left) and reduction (right) for types unsigned and signed using the resize function.

The example below shows extension and reduction for objects of type unsigned.

The next example shows extension and reduction for objects of type signed.

The following example shows unsigned and signed extension and reduction for objects of type std_logic_vector (it is assumed that there is no support for the numeric_std_unsigned package, which anyway provides only unsigned extension/reduction). Because then there is no resize function for SLV, we convert it to unsigned or signed (note that this choice can affect the result), do the resizing, and then return the result to SLV (so type cast, explained in section 7.10.2, is applied twice in each signal assignment below).

For fixed-point data, resizing is a little more complex. An example is shown below for type ufixed, where the fraction part and the integer part of signal ufix1 are both reduced to produce ufix2. If ufix1 does not fit in the space of ufix2, the latter is filled with ones (saturated); otherwise, the fraction is truncated and the round to nearest with ties rounded to nearest even (line 6 of table 1.1, detailed in table 1.2) rounding style is applied.

The following is produced by the resize function above:

Resizing floating-point data is more complex than for any other type, so a large portion of section 1.6.5 was dedicated to related details. The example below shows reduction and extension assignments for FP data.

To implement the resize function with extension above, a circuit similar to that seen in figure 1.29 is required, and this must provide not only extension but also compliance with the special cases defined in the IEEE 754 standard (table 1.3). When reduction is wanted, the round to nearest with ties rounded to nearest even (line 6 of table 1.1, detailed in table 1.2) rounding style must be applied. The reader is encouraged to review the material and examples in sections 1.6.4 and 1.6.5 and then solve exercise 7.9.

7.10    Type Conversion

There are three fundamental ways of executing type conversion in VHDL, all described next. In the examples, the following signals will be employed:

7.10.1    Automatic Conversion

Occurs when dealing directly with the base type. For example, bit and bit_vector have the same base type (bit), so a single element of the latter is automatically compatible with the former. Another example are the types std_(u)logic_vector, unsigned, signed, ufixed, sfixed, and float, which all have std_ulogic as base type, so the value of a single element (a “bit”) of one can be passed to a single element of any of the others. Examples using the signals listed above are shown next.

7.10.2    Type Cast

Consists of direct type conversion between closely related types. The most common cases are:

Between integer and real.

Between std_ulogic_vector (so std_logic_vector too), unsigned, and signed.

To perform the conversion, we write the name of the target type, followed by the object to be converted between parentheses, as shown in the examples below (the abbreviations are from table 7.1):

A very common use of type cast is in arithmetic circuits. Section 10.7 suggests that standard-logic types be used for circuit ports whenever possible, but if the circuit does arithmetic with integers, such signals should be converted internally to unsigned or signed and back to standard-logic types after the computations (to be sent out). Another example where type cast is employed is for determining the minimum number of bits needed to represent an integer (section 7.6.6).

7.10.3    Type-Conversion Functions

The last option is with a specific type-conversion function. The predefined functions for that purpose are summarized in table 7.10. As can be seen in the fourth column, the function’s general form is

Table 7.10

Predefined type-conversion functions for synthesizable types and subtypes

The argument field consists of an object of one of the types or subtypes listed in the first column. The additional specifications are not always needed, but when they are, they might have more than one option; in the cases shown, width is the vector width, L and R are the left and right index bounds, and Ewidth and Fwidth are the exponent and fraction widths. Several alias names are also available for some of the functions (consult corresponding packages).

Examples:

Note that the last column of table 7.10 shows the package of origin of each function, which must obviously be included in the packages list (figure 6.1) for the function to be used; the only exceptions are for the fixed- and floating-point packages, for which fixed_pkg and float_pkg suffice in the list if the corresponding default parameters are accepted. Observe that, for completeness, type casting cases were also included in the table as well as the type real, for which some synthesis support might exist for static values. Remember, however, that support for some packages (for fixed- and floating-point, in particular) is usually limited or inexistent in free compiler versions.

Conversion between integer and std_ulogic_vetor (or respective subtypes)

This can be done in two main ways, described below.

1)  When the numeric_std_unsigned package is supported: A single-step conversion is possible, using a type-conversion function available in that package (the abbreviations below are from table 7.1):

From INT (or subtype) to SUV (or subtype): Use line 1 or 2 of table 7.10.

From SUV (or subtype) to INT (or subtype): Use line 13 of table 7.10.

Example:

2)  When the numeric_std_unsigned package is not supported: The two-step procedure shown below, which employs a conversion function plus type cast, can be used.

From INT (or subtype) to SUV (or subtype): Use line 3 or 4 of table 7.10 plus type cast.

From SUV (or subtype) to INT (or subtype): Use type cast plus line 22 of table 7.10.

Example:

7.10.4    Strength-Stripping Functions

These functions, also called translation functions, are not exactly type-conversion functions but rather functions that covert the std_ulogic values 'L' and 'H' into '0' and '1', respectively, and also help detect the presence of metalogical values. They are described below.

Converts 'L' to '0' and 'H' to '1'. If there is any element different from these four, the entire vector is converted to the value defined by xmap, so the presence of metalogical values can be easily detected. The default value of xmap is '0'.

Converts 'L' to '0' and 'H' to '1'. Any value besides these four is converted to 'X'.

Converts 'L' to '0' and 'H' to '1'. Any value besides these four plus 'Z' is converted to 'X'.

Converts 'L' to '0' and 'H' to '1'. Any value besides these four plus 'U' is converted to 'X'.

Examples:

7.11    Type-Qualification Expressions

Qualified expressions are employed to resolve ambiguities when the compiler can not determine the type of an object value. Its general form is shown below, where the symbol ' (called a “tick” and used extensively in chapter 9 to indicate a VHDL attribute) and parentheses are required:

For example, say that we want to perform the addition sum = a + "1001", where sum and a are of type unsigned. Consulting table 9.4, we observe that when the output has to be UNS, the “+” operator supports three situations: UNS + UNS = UNS, UNS + NAT = UNS, and UNS + SU = UNS. The compiler would then be able to determine unambiguously that the type of "1001" is UNS because for NAT the vector would be written as 9 and for SU it would be a single bit.

On the other hand, if sum and a are of type integer, table 9.4 shows that when the output is INT the only option available is INT + INT = INT, so "1001" must be converted to INT. A direct conversion using the to_integer function is not possible because the type of "1001" cannot be determined unambiguously (it can be SIG, SFIX, FLO, etc.), so type qualification is needed. In summary:

As another example, say that x in the statement below is also of type integer. Consulting table 9.5, we observe that there are several types that can be compared to INT: INT itself, SIG, SFIX, FLO, and so on, so the problem again requires type qualification:

7.12    Additional Examples

We close this chapter with two complete examples that illustrate the importance of standard-logic types; more specifically, of the high-impedance ('Z') and don’t care ('−') values, which were not available in the original type bit. Do not worry about language details for now.

Example 7.1. Tri-state buffer

This example illustrates the importance of the synthesizable value 'Z' (high impedance) that was introduced in the std_logic_1164 package and needed to build tri-state buffers, like that in figure 7.4a. Observe in the truth table that when ena='0', the output node goes to a physically “floating” (disconnected) state, as seen previously in figure 1.1.

Figure 7.4
Tri-state buffer of example 7.1: (a) Intended design; (b) RTL view produced by the compiler.

A VHDL code that implements this buffer is presented below, with the package std_logic_1164 included in the packages list (line 3). A generic constant (NUM_BITS) is employed to obtain a parameterized (generic) design. In the architecture, a single statement (line 16) suffices to obtain the desired circuit. The RTL view produced by the compiler is shown in figure 7.4b.

Example 7.2. Circuit with “don’t care” values

This example illustrates the importance of the synthesizable value '−' (don’t care), also introduced in the standard-logic package (std_logic_1164), which allows the compiler to simplify boolean equations (thus saving hardware). The circuit to be designed is shown in figure 7.5a, whose truth table includes several don’t care values.

Figure 7.5
(a) Circuit and truth table of example 7.2; (b) Corresponding Karnaugh maps.

Karnaugh maps for the truth table given are shown in figure 7.5b, from which b₁=a₂' and b₀=a₁ result. Note in these equations that a₀ does not drive any logic, which would be totally different if the don’t care values were replaced with zeros, for example (the reader is invited to confirm this by modifying the Karnaugh maps of figure 7.5b).

A corresponding VHDL code is presented next, which, after compilation, shows exactly the same equations above. Standard-logic types, responsible for introducing the '−' value, were again employed (lines 7–8). Here too the reader can confirm the extra hardware consumption when the don’t care values are replaced with zeros. (Recall that, for synthesis purposes, 'X' is also interpreted as “don’t care.”)

7.13    Exercises

Exercise 7.1. Required packages

Which package(s) must be included in the packages list (at the head of a VHDL code, as indicated in figure 6.1; see also examples 7.1 and 7.2 above) to use each of the enumeration and array types listed in table 7.2?

Exercise 7.2. Possible data types

The first column of table 7.11 shows twelve signal assignments. Using the abbreviations of table 7.1, write in the second column all types that would be legal for each signal shown on the left and the corresponding number of bits (between parentheses). However, because values other than '0' and '1' should not be used for types that are intended for arithmetic circuits (UNS, SIG, UFIX, SFIX, FLO), write them off in the last column when that is the case (as in the last table line, which was filled already). Assume that there is no single-element vector (like bit_vector(0 downto 0)) and recall that integers are represented with up to 32 bits.

Table 7.11

Exercise 7.2

Assignment

Could be (Number of bits)

Should NOT be

s1 <= '0';

s2 <= −1;

s3 <= false;

s4 <= 'Z';

s5 <= 1111;

s6 <= "ZZ-Z";

s7 <= 50_000_000;

s8 <= "00" & "1111";

s9 <= (0, 1, 2, 3);

s10 <= (false, true);

s11 <= b"011_001";

s12 <= "011UX";

STR (40); SUV, SLV, UNS, SIG, UFIX, SFIX, FLO (5)

UNS, SIG, UFIX, SFIX, FLO

Exercise 7.3. Number of bits and decimal values

Write in table 7.12 the number of bits (wires) required by each signal and also the corresponding decimal value. Recall that an integer with unspecified range consumes 32 bits.

Table 7.12

Exercise 7.3

Type description

Assignment

# bits

Decimal value

integer range −100 to 100

s1 <= −15;

----------

integer_vector(1 to 3)

s2 <= (7, 0, −2**31);

----------

boolean_vector(1 to 2)

s3 <= (false, true);

----------

bit_vector(N−1 downto 0)

s4 <= (others => '0');

std_logic_vector(0 to 7)

s5 <= "0000" & "1111";

unsigned(3 downto 0)

s6 <= (others => '1');

signed(3 downto 0)

s7 <= (others => '1');

ufixed(3 downto −2)

s8 <= "110001";

sfixed(3 downto −2)

s9 <= "110001";

float(4 downto −4)

s10 <= "110100100";

Exercise 7.4. Legal versus illegal aggregations

Consider the constant and signals below, which are followed by eight assignments involving aggregates. Four of them are illegal (with one or more errors); find them and explain why.

Exercise 7.5. Legal versus illegal concatenations

Consider the same constant and signals of exercise 7.4, for which eight assignments involving concatenations are shown below. Four of them are illegal; find them and explain why.

Exercise 7.6. Resizing unsigned and signed data

Assume that "110001" is of type unsigned for parts (a)–(b) and signed for (c)–(d). Write down both the bit vector and the decimal value after:

a)  Unsigned resizing it to 8 bits.

b)  Unsigned resizing it to 4 bits.

c)  Signed resizing it to 8 bits.

d)  Signed resizing it to 4 bits.

Exercise 7.7. Resizing signed and standard-logic data

Write down the results of the six signal assignments below.

Exercise 7.8. Resizing fixed-point data

Consider the following fixed-point signal declarations and assignment:

a)  Determine amax and bmax, in binary and decimal formats.

b)  Enter in table 7.13 the decimal values of a. Is any value of a outside the range allowed for b?

Table 7.13

Exercise 7.8

Original value (a)

Resized value (b)

Binary

Decimal

Binary

Decimal

"001111.101010"

"001101.001100"

"000000.100101"

"100001.000000"

c)  Enter now the binary and decimal values for b.

d)  Finally, analyze the error between the results (b) and the original values (a).

Exercise 7.9. Resizing floating-point data

Consider the following FP signal declarations and assignment:

a)  Determine the positive values (without zero and infinity) of amin, amax, bmin, and bmax, in binary and decimal formats.

b)  Enter the decimal values of a in the first column of table 7.14. Is any value of a outside the range allowed for b?

Table 7.14

Exercise 7.9

Original value (a)

After fraction truncation and rounding (a*)

After exponent reduction (b)

Binary: (1)(011000)(1111100)

Binary:

Binary:

Decimal:

Decimal:

Decimal:

Binary: (1)(100000)(1110100)

Binary:

Binary:

Decimal:

Decimal:

Decimal:

Binary: (1)(100110)(1111011)

Binary:

Binary:

Decimal:

Decimal:

Decimal:

c)  Fill the second column of table 7.14, as done for table 1.4. Is any value of a* outside the range allowed for b?

d)  Fill the third column of table 7.14, again as done for table 1.4.

e)  Finally, analyze the error between the results (b) and the original values (a).

Exercise 7.10. Type conversion (1)

The assignments below employ signals from the list presented at the beginning of section 7.10. Examine each assignment and determine whether it employs (i) automatic, (ii) cast, or (iii) mixed (automatic + cast) type conversion.

Exercise 7.11. Type conversion (2)

Show how the type conversions below can be made by writing, for each case, a signal assignment again using signals from the list presented at the beginning of section 7.10. If necessary, use type-conversion functions (table 7.10). Assume that the package numeric_std_unsigned is not supported yet.

a)  From integer to std_logic_vector (therefore, from signal int to signal slv).

b)  From integer to signed (from signal int to signal sig).

c)  From std_logic_vector to integer (from signal slv to signal int).

d)  From std_logic_vector to signed (from signal slv to signal sig).

e)  From unsigned to std_logic_vector (from signal uns to signal slv).

f)  From std_logic_vector to sfixed (from signal slv to signal sfix).

Exercise 7.12. Strength-stripping functions

Write the result from the four operations below:

Exercise 7.13. Type qualification (1)

Say that the type of a and b is SL and that of c is UNS(3:0). Explain why the first two assignments below are illegal (what are the errors?) and why the last two are legal. (Hints: Table 9.2 and number of bits.)

Exercise 7.14. Type qualification (2)

Present two examples where type qualification (section 7.11) is necessary and explain why.

8    User-Defined Data Types

Chapter 7 described the synthesizable predefined data types (table 7.1) and a number of related concepts. This chapter completes the study of data types by describing synthesizable user-defined types and related properties.

8.1    Review of Synthesizable Predefined Types

In addition to the predefined types, VHDL also allows new types to be easily created by the user. However, before we start, a quick review of the synthesizable predefined types is presented in table 8.1 using the official classification seen in table 7.2. In the last column, representation examples are shown for all cases, making it clear why some types are called scalar (note a single value in the examples in the last column) while others are called arrays (a vector of values in the last column).

Table 8.1

Representation of synthesizable predefined types

Type class

Subclass

Type or subtype

Representation examples

Scalar types

Integer types

integer

−35 (≤32 bits)

natural

0 (≤31 bits)

positive

255 (≤31 bits)

Enumeration types

boolean

true (1 bit)

bit

'1' (1 bit)

std_ulogic

std_logic

character

'A' (8 bits)

Composite types

Array types

integer_vector

(63, −9, 0, 127)

boolean_vector

(true, false, false)

bit_vector

"1000"

std_ulogic_vector

std_logic_vector

unsigned

signed

ufixed

sfixed

float

string

"VHDL"

It is also interesting to observe that, from a “bit” (one wire) perspective, the scalar types integer (plus its subtypes) and character are not single-bit types but rather 32-bit (default) and 8-bit types, respectively; consequently, integer_vector and string are not vectors of bits but rather vectors of 32-bit (default) and 8-bit elements (we will make use of this “bit” analogy in section 8.3).

8.2    User-Defined Types

In addition to the predefined types, VHDL also allows new types to be easily created by the user. Each user-defined type described next falls into one of the following three categories: integer types, enumeration types, or array types (note that these are the same categories covered in table 8.1).

8.2.1    Integer Types

An integer type can be declared (created) as follows, where the maximum range is from –2³¹ to 2³¹–1:

The name of an integer type can be any word that obeys the criteria for VHDL identifiers described in section 5.7.6. Examples of integer type declarations are shown below.

As seen in section 7.3, a user-defined integer type inherits all operators and other functions defined for type integer. Consequently, the assignments below are legal.

8.2.2    Enumeration Types

An enumeration type consists of a list of symbolic values. It can be declared as follows:

The name can be any word that obeys the naming criteria for VHDL items described in section 5.7.6. The type values list consists of a sequence of symbolic values that can be represented using identifiers (section 5.7.6) or character literals ('0', '1', 'a', 'Z', etc.). Note that five among the synthesizable predefined types of table 8.1 are enumeration types. The definitions of the first tree are reviewed below.

User-defined enumeration types are shown next. Both examples are related to finite state machines (chapters 3, 15, 16), which is an area where user-defined enumeration types are indispensable. Each example shows a type declaration followed by a corresponding signal (s) declaration.

8.2.3    Array Types

As we know from section 7.3, an array type can be declared using the keywords type and array, as follows, where the name can be any word that obeys the criteria of section 5.7.6:

Note that eleven of the synthesizable predefined types of table 8.1 are array types. The definitions of the first tree are reviewed below (observe the keywords type and array in all of them).

Array types can be unconstrained, partially constrained, or fully constrained. While the examples above are unconstrained, the user-defined array types below are partially constrained:

Only the missing specifications need to be added when making an object declaration using partially constrained arrays. The three examples below are related to the three array types above; note the use of the reserved word open in place of an index constraint that had already been defined.

Finally, the example below shows two fully constrained array types:

Consequently, object declarations using these types do not need any additional specification:

8.3    Building and Addressing Complex Array Types

This section discusses how arrays of dimensions two and higher can be constructed and addressed in VHDL. We focus on arrays whose scalar element is a single bit, hence covering all the types listed in table 8.1 with the exceptions of integer, character, and string. (Dealing with integers is straightforward, while characters and strings, besides being rarely used for synthesis, do not participate in complex arrays anyway.) Our single bit can obviously be of type boolean, bit, std_ulogic, or std_logic.

8.3.1    Array Dimensionality

We start by describing the legal structures of arrays built from a single bit, which is a very useful information from a design perspective because it tells us the organization and number of wires needed to physically implement a given signal.

An analysis model is proposed in figure 8.1. Our scalar element, which is a single bit, is shown in (a); a 1D array is a vector of bits, shown in (b); a 1D × 1D array is a pile of vectors, shown in (c); a 2D array is a matrix of bits, shown in (d); and so on.

Figure 8.1
Data array dimensionality when using “bit” (one wire) as measurement unit.

This model also helps understand how data arrays are addressed in VHDL. For example, a single element of an 1D × 1D array is addressed using two pairs of parentheses with one specification in each pair, while a single element of a 2D array is addressed using a single pair of parentheses but with two specifications in it; likewise, 1D × 1D × 1D requires three pairs of parentheses with one specification in each, while 3D still requires just one pair but with three specifications in it.

8.3.2    Predefined 1D Arrays

The highest dimension among the predefined types whose scalar element is a single bit is 1D. The following elements of table 8.1 fall in this condition: boolean_vector, bit_vector, std_ulogic_vector, std_logic_vector, unsigned, signed, ufixed, sfixed, and float.

Note:    If we apply the model of figure 8.1 to the other types of table 8.1, the following results: 1D for integer and character (they are vectors of bits) and 1D × 1D for integer_vector and string (they are piles of vectors of bits).

8.3.3    Building 1D × 1D Arrays

As seen in figure 8.1c, a 1D × 1D array is a stack of vectors. The user-defined type called type_1D × 1D below, consisting of a fully constrained array of std_logic_vector elements, falls in this category. It is followed by a signal (called s) declaration plus three possible corresponding signal assignments. Note the use of two pairs of parentheses to access a single bit of s in the third assignment.

A detailed representation for signal s above, including indexing, is shown in figure 8.2a. Additional examples of 1D × 1D array types are shown below.

Figure 8.2
Some of the data arrays seen in the examples.

The final example shows a 1D × 1D array type constructed from a scalar (observe the incremental way in which this type is constructed: scalar → 1D → 1D × 1D):

8.3.4    Building 2D Arrays

As shown in figure 8.1d, a 2D array is a matrix of bits. The user-defined type called type_2D below, consisting of a two-dimensional unconstrained matrix of std_logic elements, falls in that category. It is followed by a signal declaration in which the ranges are specified plus three possible corresponding signal assignments. Note that a single pair of parentheses accompanies s when parentheses are needed.

A detailed representation for signal s above, including indexing, is shown in figure 8.2b.

8.3.5    Building 1D × 1D × 1D Arrays

The next user-defined type, called type_1D × 1D × 1D, is an unconstrained array of std_logic_vector elements similar to figure 8.1e. Note again the incremental way in which this kind of type is constructed (the full construction is scalar → 1D → 1D × 1D → 1D × 1D × 1D → etc.). It is followed by a signal declaration, with range specifications, plus four possible corresponding signal assignments. Note that three pairs of parentheses accompany s in the last assignment to access a single bit of the array.

A detailed representation for signal s above, including indexing, is shown in figure 8.2c.

8.3.6    Building 3D Arrays

The next type, called type_3D, is an unconstrained array of std_logic elements similar to figure 8.1f. It is followed by a signal declaration, with range specifications, plus four possible corresponding signal assignments. Note that only one pair of parentheses accompanies s when parentheses are needed.

8.4    Checking and Resetting Data Arrays

This section shows how to check whether all elements of an array are zero (or some other global value) and also how to assign zero (or some other global value) to all of its elements. The arrays can have arbitrary length and any dimension. In all examples, s represents a signal.

8.4.1    Zeroing Entire Data Arrays

The examples below show how to set all array elements to zero (or another global value). They also review how to assign arbitrary values to them (as seen in sections 8.3.3 to 8.3.6). Figure 8.1 is used as reference.

8.4.2    Checking Whether Data Arrays Contain Only Zeros

Say that we need to determine whether all elements of a certain signal are zero, raising a flag (called is_zero) when that is true. For integer types (INT, UNS, SIG, etc.), direct comparison to decimal zero is allowed, so that is the recommended solution, as illustrated below, where uns and sig are signals of types UNS and SIG, respectively:

For types SUV and SLV, type cast to UNS or SIG, followed by comparison to decimal zero, can be used:

Things are a bit more complex when dealing with signals of higher dimensions, as those listed below:

To check whether all elements of signal s1 are zero, one might be tempted to use the following:

Though that is accepted by some compilers, the appropriate aggregation is this:

This solution can be extended to any other dimension, as illustrated below for signals s3 and s4:

8.5    Classical Mistakes in Assignments

We close the study of data types, started in chapter 7, by describing common mistakes made when assigning a value or expression to a VHDL object. Such errors are divided in six categories, summarized in table 8.2 and described individually next.

Table 8.2

Common errors in assignments

Error

Comments/examples

1

Incorrect assignment symbol

Use <= for signals

Use := for variables, constants, and initial/default values

2

Type mismatch

Type must be the same on both sides of an expression

3

Invalid value or invalid representation

Incorrect use of quotation marks

Type bit cannot receive value 'Z'

4

Incorrect indexing

Incorrect use of parentheses for range specifications, incorrect range direction, index out of range

5

Size mismatch

Both sides of an expression must have same dimensionality and same number of bits in each dimension

6

Illegal aggregation or illegal concatenation

Can be tricky; see examples in sections 7.9.1 and 7.9.2

Error 1: Incorrect assignment symbol

This is the most trivial error. Recall that the assignment symbol for signals is “<=”, while for variables, constants, and initial/default values it is “:=”. Legal and illegal examples are shown below, where s represents a signal and v represents a variable.

Error 2: Type mismatch

This is one of the most common errors. Both sides of an assignment must be of the same type/subtype. For example, it is an error if one side is bit and the other is boolean, but it is fine if one side is integer and the other is natural (even though the latter in some cases would not make sense). Other examples are shown below.

Error 3: Invalid value or invalid representation

For example, a signal of type bit cannot receive the std_ulogic value 'Z'; a single-bit value must be surrounded by single quotation marks, a vector of bits requires double quotation marks, and integers are written without quotation marks. Examples are shown below.

Error 4: Incorrect indexing

This error includes the following:

1)  Incorrect use of parentheses: For example, scalars are written without parentheses, 1D × 1D arrays require two pairs of parentheses, and 2D arrays require one pair. (Incorrect use of parentheses for aggregation is error 6.)

2)  Incorrect range direction: The direction of the range must be respected. For example, if a type is declared with descending indexing (using “downto”), it must be addressed (for slicing, for example) in that same direction.

3)  Index out of range: The range bounds must be respected. For example, if the index range is 1 to 5, we cannot address an element using index 0.

Several examples are shown below.

Error 5: Size mismatch

Both sides of an assignment must have the same dimensionality and the same number of bits in each dimension. Examples are shown below.

Error 6: Incorrect aggregation and/or concatenation

Data aggregation and concatenation, studied in sections 7.9.1 and 7.9.2, respectively, can be tricky, so it is not uncommon to make mistakes when employing them. Please review the examples shown there.

Combining all errors    Additional examples are presented next and involve all kinds of errors described above. The following types and signals are used for that:

Examples of illegal assignments using expressions:

Examples of illegal assignments using specific values:

Examples of legal assignments:

8.6    Additional Examples

The examples that follow further illustrate the usage of user-defined types. Do not worry about particular language details for now.

Example 8.1. Multiplexer with a user-defined input type

This example shows how to proceed when a user-defined type is needed right at the beginning of a VHDL code (i.e., in the port field of the entity declaration).

Say that we need to implement the multiplexer of figure 8.3a (muxes were reviewed in section 1.4.1; the figure below was borrowed from figure 1.3b), which has a generic number of inputs (M) and a generic number of bits per input (N). Say also that we want to do it using a 1D × 1D type to represent the inputs. Because that kind of type is not available among the predefined types, a user-defined type is needed. (Why would integer_vector not solve our problem? Note also that that would be awkward for muxes in general anyway.)

Figure 8.3
(a) Generic multiplexer of example 8.1; (b) RTL view produced by the compiler.

A solution for this problem is presented below. The first part consists of a package, called user_defined_type_pkg, in which the intended 1D × 1D type is created (line 3); note that it is totally unconstrained in this case.

The design code starts with a use clause pointing to that package (line 2), which is assumed to be present in the project directory (work library) (it can also be typed together with the design code, exactly as shown below). Note that M and N, called NUM_INPUTS and NUM_BITS in the code, are generic constants (lines 6–7), as requested, so only their values need to be changed to implement a mux of any size. In the architecture body (lines 14–17), a single assignment (line 16) suffices to obtain the desired circuit.

The register transfer level (RTL) view produced by the compiler is shown in figure 8.3b, where several smaller muxes are used to obtain the final multiplexer (recall that the actual physical implementation can be a little different from the RTL view because it depends on the target technology/device but obviously has exactly the same functionality).

Notes:

1) As shown in sections 6.7 and 14.2, VHDL-2008 allows types to be declared in generic lists, but not for direct use in the subsequent port field;

2) Standard-logic types are usually preferred for circuit ports, which, to keep the code as simple as possible, was not done here;

3) Though less clear, this problem can also be solved using only predefined types (exercise 11.2).

Example 8.2. Vector-matrix multiplier with user-defined types

This example illustrates the construction of relatively complex data types used to build a vector-matrix multiplier (figure 8.4), which computes a × b = c, where a and c are vectors and b is a square matrix. The output is given by ci = a₁ ● b₁i + a₂ ● b₂i + a₃ ● b₃i + a₄ ● b₄i, where all elements are signed integers.

Figure 8.4
Vector-matrix multiplication of example 8.2.

A VHDL code for this problem is presented below (again, do not worry about language details yet). The first part is again a user-defined package, where two user-defined types are declared. Because the type signed is used in this package, the package numeric_std (line 3) from the ieee library (line 2) must be included in the packages list.

The design code also starts with a packages list. Because here too the type signed is used, the package numeric_std is included in the list (line 3). The other packages are math_real (line 4), needed because the functions ceil and log2 are used (line 11), and user_defined_types_pkg (line 5), which is the package just created.

The entity declaration (lines 7–15) is called vector_matrix_multiplier. It starts with a generic list (lines 9–11) with three global constants: SIZE, which specifies the number of elements in the input and output vectors and also the size of the matrix; BITS_IN, which defines the number of bits for each input and matrix element; and BITS_OUT, which informs the number of bits in each output element (note that this is indeed a dependent constant—see the discussion in section 6.7). The circuit ports (lines 13–14) consist of vectors whose individual elements are of type signed (see the type declarations in the user-defined package).

To calculate BITS_OUT (line 11), we start by calculating the number of extra bits needed in the adder array, given by EXTRA_BITS = ⎡log₂(numberof inputs)⎤ (as seen in section 1.5.4, this is the number of layers that a corresponding tree of adders would have). Additionally, we must observe that the adders receive integers after multiplication, thus with 2 ● BITS_IN bits. Therefore, the total number of bits for each element of the output vector, to avoid overflow, is BITS_OUT = 2 ● BITS_IN + EXTRA_BITS.

The architecture body (lines 17–34) starts with the declaration of a constant called MATRIX, whose individual elements are of type integer instead of unsigned because integers are easier to represent and inspect. Because we are using four bits for these elements, they must fall in the −8 to 7 range. The code proper consists of a single process (lines 23–33), which employs two nested loops to make the vector-matrix multiplications. Note in line 29 that the matrix elements are converted from integer to signed in order to participate in the equation.

8.7    Exercises

Table 8.3, with predefined and user-defined types, will be used in several of the exercises.

Table 8.3

Exercise 8.1

type type1 is array (7 downto 0) of std_logic;

type type2 is array (1 to 3) of type1;

type type3 is array (1 to 2) of type2;

type type4 is array (1 to 3, 7 downto 0) of std_logic;

signal s1: bit;

signal s2: bit_vector(0 to 4);

signal s8: ufixed(3 downto -4);

signal s9: sfixed(5 downto -4);

signal s3: boolean_vector(1 to 8);

signal s10: float(4 downto -4);

signal s4: std_logic;

signal s5: std_logic_vector(7 downto 0);

signal s11: type1;

signal s12: type2;

signal s6: unsigned(7 downto 0);

signal s7: signed(5 downto 0);

signal s13: type3;

signal s14: type4;

Exercise 8.1. Dimensionality of data arrays

Using figure 8.1 as reference, list the dimension of each signal (s1 to s14) of table 8.3. Write also the number of wires needed to physically implement each signal.

Exercise 8.2. User-defined 1D × 1D array types

Create a 1D × 1D array type (call it type_1D × 1D) as specified below, with size 4 × 8. In part (a), include an illustration similar to figure 8.2a. (Example using type ufixed: type type_1D × 1D is array (3 downto 0) of ufixed(4 downto -3);)

a)  Using type signed.

b)  Using type bit_vector.

c)  Using type bit (recall the incremental way in which 1D × 1D × 1D × … arrays are constructed).

Exercise 8.3. User-defined 2D array types

Create a 2D array type (call it type_2D), as specified below, with size 4 × 8. In part (a), include an illustration similar to figure 8.2b.

a)  Using type boolean.

b)  Using type std_logic.

Exercise 8.4. User-defined 1D × 1D × 1D array types

Create a 1D × 1D × 1D array type (call it type_1D × 1D × 1D), as specified below, with size 2 × 4 × 8. In part (a), include an illustration similar to figure 8.2c. See comment in part (c) of exercise 8.2.

a)  Using type sfixed.

b)  Using type std_logic_vector.

c)  Using type std_logic.

Exercise 8.5. Legal versus illegal assignments (1)

Table 8.4 shows assignments using signals from table 8.3. Four are legal, four are illegal with one error, and four are illegal with two errors. Identify the illegal cases and explain the errors, including their category numbers (table 8.2).

Table 8.4

Exercise 8.5

Assignment

Legal or illegal? (explain)

1

s1 := s2(4);

2

s3 <= b"0000_1111";

3

s5 <= (0 => s4, others => '-');

4

s7 <= signed(s5);

5

s8(3 downto 0) <= s9(0 downto −4);

6

s8(3) <= s9(−1);

7

s9 <= '0', "1111", "0000";

8

s10 <= (7 => 1, others => '0');

9

s12(1,7) <= s3(8);

10

s13(1)(1) <= s11;

11

s13(2) <= (others => (others => '0'));

12

s14(1,1) <= s1;

Exercise 8.6. Legal versus illegal assignments (2)

Table 8.5 shows assignments using signals from table 8.3. Again, four are legal, four are illegal with one error, and four are illegal with two errors. Identify the illegal cases and explain the errors, including their category numbers (table 8.2).

Table 8.5

Exercise 8.6

Assignment

Legal or illegal? (explain)

1

s1 <= s13(1,2,3);

2

s2 <= ('0', s1, others => 'Z');

3

s5 <= s4 & std_logic_vector(s7) & '0';

4

s5 := s11;

5

s5 <= s13(3)(1);

6

s6 <= ('1', s4, others => '0');

7

s7 <= unsigned(s5);

8

s8 <= to_ufixed(s6, 3, -4);

9

s10 <= to_float(s8, 4, 4);

10

s12 <= s11, s11, s11;

11

s13(1)(1) <= s5;

12

s14(3)(0) <= s5(0);

Exercise 8.7. Checking whether a data array contains only zeros

Show how to determine whether the signals below, from table 8.3, contain only zeros.

a)  Signal s11.

b)  Signal s12.

c)  Signal s13.

d)  Signal s14.

Exercise 8.8. Setting all elements of a data array to zero

Show how to set all bits of the signals below, from table 8.3, to zero.

a)  Signal s11.

b)  Signal s12.

c)  Signal s13.

d)  Signal s14.

Exercise 8.9. Checking arrays of integers

Show how to determine whether all elements of signals s1 and s2 below are zero.

Exercise 8.10. Resetting arrays of integers

Show how to set all elements of signals s1 and s2 of exercise 8.9 to zero.

Exercise 8.11. Checking whether an integer is odd

Show how to determine whether signal s below is odd.

9    Operators and Attributes

The purpose of this chapter, along with the preceding chapters 5 to 8, is to lay the foundations of VHDL, so in the next chapter we can start dealing with actual circuit designs. This sequence is very important because it is impossible—or not very productive, at least—to write code efficiently without knowing objects, types, operators, and attributes well.

In summary, this chapter covers the following:

The study of operators, including all six predefined categories plus user-defined operators.

The study of attributes, including all four predefined categories plus user-defined and synthesis attributes.

The study of group and alias declarations.

9.1    Predefined Operators

The predefined VHDL operators (involving the packages and types listed in table 7.1) are divided in six categories:

Logical operators

Arithmetic operators

Comparison (relational) operators

Shift operators

Concatenation operator

Condition operator

The “names” of all predefined operators (not, and, +, *, /=, sll, etc.) are listed in table 9.1. Each category is described next. As will be seen, there are many options, so be aware that your compiler might not support all of them (yet).

Table 9.1

Predefined VHDL operators

Category

Operators

Comments

Logical

not

The not operator

and, nand, or, nor, xor, xnor

Regular and reduction logical operators

Arithmetic

−

Negation operator

+, −, *, /, **, mod, rem, abs

Regular arithmetic operators

Comparison

=, /=, >, <, >=, <=

Regular comparison operators

?=, ?/=, ?>, ?<, ?>=, ?<=

Matching comparison operators

minimum, maximum

Additional comparison functions

Shift

sll, srl, rol, ror

Logical shift operators

sla, sra

Arithmetic shift operators

Concatenation

&

Array-forming operator

Condition

??

Converts bit or std_ulogic to boolean

9.1.1    Logical Operators

Logical operators are used to perform logical operations between objects of all predefined types based on bit, boolean, and std_ulogic. They are the following:

All binary operators have the same precedence, and so do all unary operators. However, the unary set has precedence over the binary set.

The binary operations can involve two single bits, or two vectors, or still one bit and one vector. In the last case, the single bit operates against all bits in the participating vector. Examples in all three cases are shown below.

Examples of binary logical operations between two bits:

Examples of binary logical operations between two vectors:

Examples of binary logical operations between a single bit and a vector:

Contrary to binary operations, unary operations involve a single argument, which can be a single bit or a single vector. They consist of the not operator (which inverts all bit values) and the reduction and, nand, or, nor, xor, and xnor operators (which reduce a vector of bits to a single bit).

Examples of unary logical operations:

Examples with legal and illegal unary logical operations:

A detailed view of logical operators is presented in table 9.2, which includes the main synthesizable types supported by such operators (the abbreviations are from table 7.1). Recall that subtypes (like std_logic and std_logic_vector) inherit all operators defined for their parent types.

Table 9.2

Logical operators (including output sizes)

Examples of legal and illegal assignments, based on table 9.2, are shown below.

The and, or, xor, and not tables    Note in table 9.2 that, except for the cases defined in package standard, all other types with support for logical operators have std_ulogic as base type, so for them any of the nine logic values can be used as inputs to logical operations (even though this would not make sense in actual designs, where only values from the "01Z−" set are recommended). The output, however, is always reduced to the "UX01" set, as defined by the and, or, xor, and not tables, summarized in table 9.3 (for synthesis purposes, metalogical values might happen to be considered zero).

Table 9.3

Summary of and, or, xor, and not tables

Examples of logical operations with input values other than zeros and ones:

9.1.2    Arithmetic Operators

The predefined arithmetic operators are listed below. Note that only the negation and absolute-value operators are unary (single operand).

As will be described in section 10.7, the following five data types are recommended for implementing arithmetic circuits in VHDL:

For integer arithmetic: unsigned and signed.

For fixed-point arithmetic: ufixed and sfixed.

For floating-point arithmetic: float.

The other type with strong arithmetic support is integer. It is recommended, however, that the use of this type be restricted to roles like circuit constants, pointers, timers, etc. (not in circuit ports or conventional arithmetic operations). Also, recall that, except for float, the other four types listed above employ two’s complement to represent negative values.

A detailed view of arithmetic operators is presented in table 9.4, which includes the main synthesizable types supported by such operators. Table 9.4 includes also the size or range required at the output in each operation. That table will be used as reference in the descriptions that follow.

Table 9.4

Arithmetic operators (including output sizes or ranges)

a) Addition and subtraction operators (+, −)    As shown in table 9.4, the addition and subtraction operators are available for all types listed above. Additionally, the requirements for these two operators are exactly the same. For example, for the type integer (and so also for its subtypes), the first line of the table tells us that when we want the output to be an integer, only integers can participate in the expression (i.e., INT ± INT = INT). That line tells us also, via comment (1), that for integers any number of bits can be used for the inputs and output (it is up to the user to specify the right ranges), but the minimum number of bits at the output such that overflow never occurs is Outp'length = maximum(L'length, R'length) + 1 (in other words, it should be equal to the number of bits in the largest input plus one; L and R are the left and right inputs, respectively).

The next type in table 9.4 is unsigned, for which the following options exist when using addition or subtraction: UNS ± UNS = UNS, UNS ± NAT = UNS, NAT ± UNS = UNS, UNS ± SU = UNS, and SU ± UNS = UNS. In other words, when the desired output is UNS, one of the inputs has to be UNS, but the other can be UNS, NAT, or SU. The output range for the UNS ± UNS case, normalized to “ … downto 0”, is given in comment (8) as “maximum(L'length, R'length)−1 downto 0” (so the size of the output must be exactly the same as that of the largest input, so here overflow can occur).

Note that all arithmetic operations that involve only floating-point data employ the range expressed in comment (28), so truncation and rounding might be necessary. This is done automatically by the compiler using the rounding procedure referred to in section 7.6.5 and detailed in section 1.6.5.

A final comment regarding synthesis support. Be aware that some compilers do process certain operations that are not in table 9.4, but then the result might not be as expected. For example, UFIX + INT = UFIX is not included in table 9.4, but some compilers do synthesize it with some modifications, such as by eliminating the integer’s negative range or by ignoring the integer’s MSB. In summary, it is important to observe the official definitions of all VHDL operators.

In the examples that follow, int is a signal of type INT, sig is a signal of type SIG, and so on (the abbreviations are from table 7.1).

Examples of legal additions and subtractions:

Examples of illegal additions and subtractions:

More examples of illegal additions and subtractions (“:” means “downto”):

b) Multiplication operator (*)    Table 9.4 shows that the multiplication operator (*) also accepts all types listed earlier. The main difference with respect to addition and subtraction is in the size of the result; for example, for integers, any number of bits is again allowed, but the minimum number to prevent overflow, expressed in comment (2), is Outp'length = L'length + R'length (in other words, the number of bits at the output must be the sum of the number of bits at the inputs).

For the next type, which is unsigned, two options exist: UNS*UNS = UNS and UNS*NAT = UNS. The number of bits in the UNS*UNS option, for example, is given in comment (11) as L'length + R'length-1 downto 0 (so here too the number of bits at the output must be equal to the total number of bits at the inputs).

Jumping now to type sfixed, we observe that again there are two options: SFIX*SFIX = SFIX and SFIX*INT = SFIX. The number of bits in the SFIX*SFIX option, for example, is given in comment (18) as L'left + R'left + 1 downto L'right + R'right (so the number of bits at the output must again be equal to the total number of bits at the inputs).

A similar analysis, for the other types of table 9.4, is left to the reader. Examples with legal and illegal multiplications follow.

Examples of legal multiplications:

Examples of illegal multiplications:

c) Division operator (/)    Table 9.4 shows that support for the division operator (/) is similar to multiplication. Again, the main difference is in the size of the result; for example, for integers, the minimum number of bits needed to prevent overflow is usually considered to be that of the dividend, as shown in comment (3), though in the extreme case of [−2**(NUM_BITS − 1)]/(−1) overflow can occur.

For the next type, which is UNS, the following options exist: UNS/UNS = UNS, UNS/NAT = UNS, and NAT/UNS = UNS. The number of bits in the UNS/NAT and NAT/UNS options, for example, is given in comment (9) as UNS'length-1 downto 0, so the output width must be the same as that of the UNS operand (the NAT value is converted to UNS with the same range as the UNS operand).

The division operator for types INT, UNS, and SIG returns the quotient rounded down. When the result is negative, the magnitude is considered for rounding purposes; for example, 3/5 (= 0.6) → 0, −3/5 (= −0.6) → 0, 9/5 (= 1.8) → 1, and −9/5 (= −1.8) → −1.

For types UFIX, SFIX, and FLO, the rounding procedure is that referred to in section 7.6.4 or 7.6.5, and detailed in sections 1.6.4 and 1.6.5.

d) Exponentiation operator (**)    As shown in table 9.4, this operator is available only for type integer (and its subtypes, obviously). As indicated in comments (4)–(5), synthesis support is usually limited to constant exponent or constant base (like y<=x**7 or y<=3**x;), or even less than that (both the base and the exponent constant).

The exponentiation operator is helpful for specifying static parameters, like the bounds of an integer as a function of its number of bits, as illustrated below:

e) Remainder and modulo operators (rem, mod)    The x rem y operation returns the remainder of x/y with the sign of x. An equivalent equation is x rem y = x − (x/y)*y.

The x mod y operation returns the remainder of x/y with the sign of y. An equivalent equation is x mod y = (x rem y) + α*y, where α = 1 when the signs of x and y are different or α = 0 otherwise. Consequently, x mod y = x rem y when x and y have the same sign.

These two operators can also be represented by the following equations:

dividend = quotient ● divisor + rem (remainder is called rem when it has the sign of dividend)

dividend = quotient ● divisor + mod (remainder is called mod when it has the sign of divisor)

Examples with integers:

Examples with type signed:

f) Negation and absolute-value operators (−, abs)    The negation operator inverts the sign of a given object value by applying two’s complement to it and the absolute-value operator returns its magnitude by applying two’s complement to it if its value is negative. Note in table 9.4 that these operators are predefined for types INT, SIG, SFIX, and FLO; while an extra bit is allowed for INT and is mandatory for SFIX (for sign extension), for type SIG the output must have the same length as the input and for FLO this is not an issue. Consequently, for SIG, an incorrect result can occur; for example, for a five-bit input (−16 to 15 range), the result of −(−16) or of abs (−16) is −16, which is obviously wrong. The next example, which is for SFIX, is error-free: −("1000.00") = "01000.00" (i.e., −(−8.0) = 8.0). Recall that unary operators have precedence over binary operators, so the following is true: abs a + b = (abs a) + b.

9.1.3    Comparison (Relational) Operators

Comparison operators, also called relational operators, are used to compare and order single- and multi-bit values. They are listed below, along with the minimum/maximum functions, which also act as comparators.

The regular comparison operators assume that all nine std_ulogic symbols (enumeration literals) are distinct values, so the equality comparison between '1' and 'H', for example, returns false. The first difference in the matching comparison operators is that it considers logic levels rather than the enumeration literals, so the comparison between '1' and 'H' now returns '1'. Moreover, it returns '1' for any comparison involving the “don’t care” value, which can be helpful. The comparison results produced by the matching equality operator are summarized below:

For logic level low: '0' to '0', '0' to 'L', and 'L' to 'L' return '1'.

For logic level high: '1' to '1', '1' to 'H', and 'H' to 'H' return '1'.

For “don’t care”: equality comparison of '−' to any other value returns '1'.

All other cases return 'U' or 'X' or '0'.

Another important difference between regular and matching relational operators is the fact that the result in the latter is an element type, while in the former it is a boolean value. For example, a matching comparison between two bit_vector elements returns a bit value and between two signed elements returns a std_ulogic value. This reduces the verbosity of comparison expressions (though at the expense of clarity), as illustrated in the example below.

A detailed view of comparison operators is presented in table 9.5, which includes the main synthesizable types supported by such operators. Note that the result (output) in the first part (regular comparators) is always a boolean, while the result in the second part (matching comparators) is always an element type (either bit or std_ulogic). Observe also that the result in the third part (minimum/maximum functions) is one of the elements that participate in the comparison.

Table 9.5

Comparison operators (main options)

The cases of bit_vector and std_ulogic_vector are a little different from the others because they do not follow the respective equivalent decimal values. First, for equality and inequality comparisons, the vectors must have the same length, otherwise the result will be automatically false. Second, vectors of different lengths are allowed for the other operators, but resizing does not occur; instead, the rightmost bits of the longer vector are discarded. However, if then the vectors being compared become equal, the longer vector (that that was truncated) is considered larger than the other (so there never are “equal” vectors when their sizes are different). These aspects are illustrated in the examples below.

Examples using BV or SUV:

A final feature that is common to BV and SUV is that, for the case of matching comparison, only the equality (?=) and inequality (?/=) operators are allowed (comment 1 in table 9.5).

Observe in table 9.5 that for all other SU-based types (UNS, SIG, UFIX, etc.) full support exists for both regular and matching comparisons. Vectors of different sizes are allowed in all comparisons, but here the shorter vector is resized (see how the resize function works in section 7.9.3) to the length of the longer vector before any comparison takes place, so corresponding decimal values are indeed compared (one more reason why these types are recommended for building arithmetic circuits). Note that consequently vectors of different sizes can now be “equal.” These aspects are illustrated in the examples below.

Examples using UNS:

Examples using SIG:

Finally, we refer to the minimum and maximum functions, shown in the last part of table 9.5. Since they employ comparison operators, the rules for them are those just mentioned (different vector sizes are allowed, sign-extension is performed automatically).

Examples using minimum and maximum operations with type SIG:

Other examples, where int is a signal of type INT, sfix is of type SFIX, and so on:

9.1.4    Shift Operators

As seen in table 9.1, shift operators can be logical or arithmetic. The complete list is shown below.

Table 9.6 shows the options for these operators. Note in the second column that they are predefined for most synthesizable predefined array types of table 7.1. Observe also that the amount of shift is always determined by an integer (positive or negative), regardless of the type of the data being shifted, as illustrated below.

Table 9.6

Shift operators

9.1.5    Concatenation Operator

Another (trivial) way of implementing shift operations is to use the concatenation operator (&):

The concatenation operator (&) is employed to build or enlarge data arrays. It was studied already, in section 7.9.2, which the reader is encouraged to review.

9.1.6    Condition Operator

The condition operator (??) converts a bit or std_ulogic value to boolean. For the former, '1' becomes true and '0' becomes false; for the latter, '1' and 'H' become true, while all the others become false.

Example:
The two statements below are equivalent; x is either of type bit or std_ulogic (or its subtype, std_logic, of course). But see comment after the example.

The condition operator is implicitly applied in most cases, so we do not need to write the question marks. That is the case in statements like when, if, wait (after until), loop (after wait or next when or exit when), and assert.

Examples:

9.2    User-Defined Overloaded Operators

We conclude the study of VHDL operators by describing overloading.

An overloaded operator is one for which more than one construction (under the same name) exists. This can be easily observed in the previous tables, which show several implementation options for the same operator. Take the arithmetic “+” operator, for example; as shown in table 9.4, it can receive two integers, producing an integer as result; or two unsigned values, producing another unsigned value as result; and so on. That means that, in the corresponding packages, functions declared as shown below are available, where L and R represent again the left and right operands:

The user can further overload an operator. For example, “+” does not include addition between bit and integer, which can be easily implemented with the functions below (functions will be studied in chapter 14), which together cover both orderings of the operators (i.e., INT + BIT = INT and BIT + INT = INT):

This new version of "+" could then be used as follows:

9.3    Predefined Attributes

Having concluded the study of VHDL operators, we pass now to the study of VHDL attributes, which are a way of retrieving information about certain named entities (a named entity is any item to which we can give a name, like types, signals, variables, and so on), the returned value always being a constant. The predefined attributes are divided in four categories: attributes of scalar types, of array types and objects, of signals, and of named entities in general. These attributes are always called using the tick symbol (').

9.3.1    Attributes of Scalar Types

These attributes provide static information regarding scalar types (table 7.2) or objects of such types. They are summarized in table 9.7, where T represents a type and x is a type value or a position index.

Table 9.7

Attributes of scalar types

Name

Result type

Result

T'base

Any (sub)type T

Base type of T

T'left

Same as T

Leftmost value of T

T'right

Same as T

Rightmost value of T

T'low

Same as T

Smallest value of T

T'high

Same as T

Largest value of T

T'ascending

boolean

True if range of T is ascending

T'image(x)

string

String representing the value of x

T'value(x)

Base type of T

Value of T whose string representation is given by x

T'pos(x)

integer

Position number of the value x of T (a value)

T'val(x)

Base type of T

Value whose position number in T is x

T'succ(x)

Base type of T

Value whose position number in T is x + 1

T'pred(x)

Base type of T

Value whose position number in T is x − 1

T'leftof(x)

Base type of T

Value on the left of value x of T

T'rightof(x)

Base type of T

Value on the right of value x of T

O'subtype

Subtype

Fully constrained subtype of O

Ascending range: T'left = T'low, T'right = T'high

Descending range: T'left = T'high, T'right = T'low

Example using an integer type (called temperature):

Example using an enumeration type (called color):

9.3.2    Attributes of Array Types and Objects

These attributes provide static information about arrays of types and of objects of classes constant, signal, and variable. They are summarized in table 9.8, where A represents an array type or an array object and n is its nth index range. Note that n is necessary only when addressing arrays with dimension higher than 1D, as illustrated in the next example. These attributes are highly helpful for constructing generic/parameterized codes.

Table 9.8

Attributes of arrays

Name

Result type

Result value

A'left[(n)]

integer

Left bound of the nth index range of A

A'right[(n)]

integer

Right bound of the nth index range of A

A'low[(n)]

integer

Lower bound of the nth index range of A

A'high[(n)]

integer

Upper bound of the nth index range of A

A'range[(n)]

Integer range

Range of the nth index range of A

A'reverse_range[(n)]

Integer range

Reverse range of the nth index range of A

A'length[(n)]

integer

Number of values in the nth index range of A

A'ascending[(n)]

boolean

True if nth index range of A is ascending

A'element

Of elements of A

Type of elements of A

Example using a 2D type (called matrix):

Example using a 1D signal (called x):

9.3.3    Attributes of Signals

These attributes provide dynamic information about signals (they monitor signal changes and other actions). They are summarized in table 9.9, where S represents a signal and t represents a time interval. The 'event attribute is essentially the only one used in synthesis (the others are used in simulation), though 'stable is also synthesizable. Details about 'event are given in section 12.2.

Table 9.9

Attributes of signals

Name

Result type

Result value

S'delayed[(t)]

Base type of S

Signal equivalent to signal S delayed t time units

S'stable[(t)]

boolean

True when no event has occurred on signal S for t units of time

S'quiet[(t)]

boolean

True when no transaction has been scheduled for signal S for t units of time

S'transaction

bit

A bit that toggles in each simulation cycle in which S becomes active (transaction scheduled)

S'event

boolean

True when an event (signal change) occurs on S

S'active

boolean

True if a transaction has been scheduled for S

S'last_event

time

Amount of time since last event occurred on signal S

S'last_active

time

Amount of time since last time signal S was active

S'last_value

Base type of S

Value of signal S previous to the last event (if no event occurred, returns current value of S)

S'driving

boolean

True if process is driving S

S'driving_value

Base type of S

Value of the driver for S in the containing process

9.3.4    Attributes of Named Entities

The last three predefined attributes are used to produce strings with information about generic named entities that might be helpful in simulation. They are listed in table 9.10, where E represents the entity’s name.

Table 9.10

Attributes of named entities

Name

Result type

Result value

E'simple_name

string

String representing the simple name, character literal, or operator symbol of the named entity E

E'instance_name

string

String describing the hierarchical path down to the named entity E, including the names of instantiated design entities

E'path_name

string

Same as above, but excluding the names of instantiated design entities

9.4    User-Defined Attributes

Besides the predefined attributes (seen in the previous section), VHDL also allows users to create their own attributes, which are used to decorate named entities (constant, signal, variable, type, subtype, function, procedure, entity, architecture, package, and so on) with additional information.

To create a user-defined attribute, a declaration plus a specification are needed, as shown in the simplified syntax below.

The attribute declaration (first line) simply defines the name and type of the user-defined attribute. The attribute specification (second line) defines the name of the entity to be decorated (can be a simple name, a character literal, or an operator symbol), the entity class to which the decorated entity belongs (constant, signal, variable, type, subtype, function, and so on), and finally the attribute expression or value.

Example:

In the first line of the code below, a signal named number_of_pins of type positive is declared. In the second line, an attribute called pins, whose type is also positive, is declared. In the third line, this attribute, with a value of 4 (thus pins = 4), is associated to the entity named nand3, whose class is component. Finally, in the fourth line, the attribute pins of nand3 is passed to the signal that was specified in the first line, resulting number_of_pins = 4.

The attribute specification can include a signature, whose purpose is to identify among any entities that have the same name (overloaded) the intended target. This situation can arise, for example, when using overloaded subprograms (chapter 14).

Example:

Say that our entity class is procedure, whose name is sort, which assigns the smaller and larger of a and b to x and y, respectively. However, assume that two versions of sort are available—one interfacing using type integer, the other interfacing with type unsigned—that is:

In this case, to clarify matters, a signature can be used, which consists of including in the specifications the data types used in the procedure, as shown below:

Consequently, calls to these procedures could now be as follow:

9.5    Synthesis Attributes

We saw in sections 9.3 and 9.4 predefined (hence standard) and user-defined (thus nonstandard) VHDL attributes, which are design related. We turn now to synthesis-related attributes, whose purpose is to communicate with the compiler. Some of these attributes are standardized (by the IEEE 1076–6 standard and its rtl_attributes package), like the attributes keep, combinational, rom_block, ram_block, and so on, while others are provided as part of synthesis tools, either as additional attributes or as attributes that may replace officials attributes. As an example, the synthesis attribute that specifies the encoding style for the states of a finite state machine (FSM) is called fsm_state in the document above, but it is called fsm_encoding in Vivado and syn_encoding in Quartus Prime, with internal options that are slightly different.

The following cases are described next:

Attributes for defining the encoding style of FSMs

Attributes for building safe FSMs

Attribute for preventing logic circuit simplifications

Attributes for defining the implementation style of inferred ROM and RAM memory

The declarative part of the architecture is an appropriate place for declaring synthesis attributes.

9.5.1    State Machine Encoding Attributes

Enumeration types are indispensable when designing FSMs because, as we saw in chapter 3, the states are “named,” so we must either tell the compiler how we want the states to be encoded or let the compiler choose the encoding for us.

As seen in section 2.6.5 (and 3.11), the main encoding styles are sequential, one-hot, and gray. The style can be set directly in the compiler’s setup, but if we want to guarantee that a certain encoding style is adopted, an attribute should be included in the code because it overrides the compiler’s setup. As already mentioned, such attribute is called fsm_state in the IEEE 1076.6 standard, fsm_encoding in Vivado, and syn_encoding in Quartus Prime (by the way, note that fsm_state is definitely not a good name for this attribute, while fsm_encoding is the best choice—see section 5.8). The last two are described below.

fsm_encoding Its options are "sequential", "one_hot", "gray", "johnson", and "auto" (default). The first four we know well; the last one is the default option, based on heuristics that try to optimize the implementation.

A typical example is shown below. Because "one_hot" is used, the following should result: A = "00001", B = "00010", C = "00100", D = "01000", E = "10000" (hence requiring five DFFs to store the machine’s state).

syn_encoding Its options are "sequential", "one-hot", "gray", "johnson", "compact" (minimal bits), "auto" (default), and user-encoded. Again, "auto" is the default, currently employing "sequential" for FSMs with up to four states, "one-hot" for five to forty-nine states, or "gray" for fifty or more states.

A typical example is shown below, this time using the user-encoded option (the encoding values are entered directly in the attribute specification).

We will make extensive use of these attributes in chapters 15 and 16, which deal exclusively with FSM implementation.

9.5.2    Safe State Machine Attributes

These attributes tell the compiler that a safe state machine (seen in section 3.9) is wanted. In other words, it specifies which action the compiler should take in case an invalid state occurs, so extra logic is added to the circuit during the synthesis process. This attribute is called fsm_safe_state in Vivado, while in Quartus Prime it is not a separate attribute but rather an additional option of the syn_encoding attribute seen above. Both cases are described below.

fsm_safe_state Its options are "auto_safe_state", "reset_state", "power_on_state", and "default_state". It employs Hamming codes for single-error detection (Hamming-2) or single-error detection plus correction (Hamming-3). The latter is used in the "auto_safe_state" mode, while the former is used in the others. A common choice is "reset_state", which forces the machine to the reset state in case a state that does not belong to the machine occurs. An example is shown below.

Safe As mentioned, safe is just an additional option in the syn_encoding attribute of Quartus Prime. It forces the machine to its reset state if an invalid state is detected. An example is shown below; note that "safe" and "sequential" are declared simultaneously.

9.5.3    Keep-Logic Attribute

The purpose of the keep attribute is to tell the compiler not to simplify (suppress) the listed nodes. In other words, it forbids certain combinational logic simplifications. Its name (keep) and usage is the same in Vivado and Quartus Prime.

Example 9.1. Delay line built with attribute keep

As an example, say that we want to implement the delay line of figure 9.1a, consisting of a series of four inverters with intermediate nodes a, b, and c. A corresponding VHDL code is shown below, with the keep attribute in lines 10–11. If the code is synthesized without it, the circuit of figure 9.1b results, whereas the inclusion of lines 10–11 leads to the circuit of figure 9.1c. (A word of caution: due to routing delays and temperature variations, the actual delay is poorly predictable in FPGAs; better estimates may be possible in ASICs.)

Figure 9.1
(a) A delay line implemented (b) without and (c) with the keep attribute.

9.5.4    ROM and RAM Implementation Attributes

These attributes define how and where the fitter should implement inferred ROM and RAM memory. They are called rom_style and ram_style in Vivado and romstyle and ramstyle in Quartus Prime. If no attribute is specified, it is left to the compiler to determine heuristically the best alternative.

An example is shown below for the ram_style attribute. Its options are "block" (implements the inferred RAM in memory blocks) and "distributed" (implements it in logic elements’ LUTs). The options for rom_style are the same.

9.6    Group

The user-defined attributes seen in section 9.4 apply to individual named entities. To apply an attribute to multiple entities, VHDL provides the group construct. To use it, a group-template declaration plus a group declaration are needed, as shown in the simplified syntax below.

The template declaration (first line) defines the template name and the entity classes (can be constant, signal, variable, type, subtype, function, procedure, and so on) involved. The box symbol (<>) indicates an arbitrary number of constituents from the same entity class; for example, “signal <>” means an undetermined number of signals in that position in the list. The box symbol can be used only once, so it has to be in the last entity class declared.

The group declaration (second line in the syntax above) contains the name chosen for the group, followed by the template name, and then the constituents’ names (simple names or character literals).

Example:

Below, a group template called test_signals is created and contains three signals. Next, a group called register is declared; this group employs three signals, called clock, reset, and enable. Finally, the third line illustrates the use of such a group in an attribute declaration.

9.7    Alias

We conclude this chapter by describing the usage of aliases in VHDL.

An alias declaration defines an alternate name for an existing named entity or for part of it. It can be used for data objects (constant, signal, variable, and file) as well as for nonobject items. The most common place for alias declarations is the declarative region of subprograms (just check the subprograms located in the official VHDL packages), followed probably by the declarative region of architectures; the most commonly aliased entities are data objects.

Simple object aliasing    The purpose in this case usually is to give the object a simpler and/or more meaningful name. Such an alias can be declared as follows:

Examples:

Object aliasing with type modification    This case is used when the original type must be modified. A typical example is when the range bounds are changed. The corresponding syntax is the following:

Example:

Nonobject aliasing    Entities that do not represent data can also be aliased. The syntax is the same as for simple object aliasing, except for subprograms and enumeration literals when they require a signature, as shown below:

Example:

Below, aliasing is applied to an overloaded subprogram (a procedure, in this case) in order to distinguish one version from the other.

9.8    Exercises

In the exercises below, the names of the signals indicate their types, based on the abbreviations of table 7.1; for example, int is a signal of type INT, slv is a signal of type SLV, and so on. The character “:” means “downto”.

Exercise 9.1. Logical operators (1)

Assume that a="10101" and b = "00111". Calculate the following:

a)  a and b

b)  a xnor b

c)  a or b or "00000"

d)  (not a and b) xor '1'

e)  or a

f)  '1' nand b

Exercise 9.2. Logical operators (2)

Half of the assignments below are illegal; find them and explain why. For the legal assignments, write the number of bits in the result (assume that all input vectors have length N bits). (Hint: Table 9.2.)

a)  bv <= bv and bv and bv;

b)  slv <= slv nand slv nand slv;

c)  int <= not int;

d)  sl <= and (slv xor slv);

e)  slv(0) <= su or sl or sl;

f) bv <= bv or slv;

g) slv <= not slv and slv and slv;

h) slv <= su or su or su;

i) uns <= uns xnor uns;

j) sl <= not slv and slv;

Exercise 9.3. Logical operators (3)

Determine the results of the expressions below. Enter two answers in each line, the first based on table 9.3, the second with metalogical values ('U', 'X', 'W', '-') synthesized as zeros.

Exercise 9.4. Arithmetic operators (1)

Assume that a and b are two integers in the −16 to 15 range. For each expression below, (1) write the minimum and maximum values achievable, (2) write the minimum number of bits needed for the result to be always overflow free, and (3) calculate the results using that number of bits for the case of a = 14 (= "01110") and b = −4 (= "11100"), showing both the decimal and binary values.

a)  a–2*b

b)  a*b

c)  a/b

d)  (a/b)*b

e)  (a*b)/b

f)  a rem b

g)  a rem (−b)

h)  a mod b

i)  –a mod b

j)  abs b

k)  –b

l)  a**2

Exercise 9.5. Arithmetic operators (2)

Half of the assignments below are illegal; find them and explain why. (Hint: See table 9.4.)

a)  slv <= slv + slv;

b)  int <= int − nat + 5;

c)  int <= nat − "1000";

d)  ufix <= -ufix + nat;

e)  sig <= int + nat*sig;

f)  uns <= abs(sig);

g)  int <= 3*int + (int rem int);

h)  ufix <= 2*int + ufix;

i)  sig <= int - sig;

j)  sig <= int + signed'("1000");

k)  int <= int + signed'("1000");

l)  int <= int + to_integer(signed'("1000"));

Exercise 9.6. Arithmetic operators (3)

Half of the assignments below are illegal; find them and explain why. (Hint: See table 9.4.)

a)  uns(7:0) <= uns(1:0) –uns(7:0);

b)  ufix(6:-6) <= ufix(4:-6) + ufix(6:-4)

c)  ufix(7:-6) <= ufix(4:-6) - ufix(6:-4)

d)  flo(6:-6) <= flo(4:-6) * flo(6:-4);

e)  uns(8:0) <= uns(1:0) –uns(7:0);

f)  flo(10:-6) <= flo(4:-6) * flo(6:-4);

g)  sig(3:0) <= sig(7:0) mod sig(3:0);

h)  sig(7:0) <= sig(7:0) rem sig(3:0);

Exercise 9.7. Comparison operators (1)

Say that signals a, b, c, and d are of type SIG. Determine the value of each expression below and inform whether the comparison returns true or false for a = "0111" (= 7), b = "1010" (= −6), c = b"0100_0000" (= 64), and d = b"1111_1111" (= −1). Assume that overflow never occurs.

a)  −a < c/b

b)  a*(abs b) –2*b <= c

c)  (c/a)*a = (c*a)/a

d)  (7*a) rem b = (7*a) mod b

e)  d > "1111"

f)  minimum(a, b) < (a+b)/2 for any a, b

Exercise 9.8. Comparison operators (2)

Write down and explain the results of the comparisons below.

a)  For type BV or SLV:

"0111" > "011100"

"0111" > "001111"

maximum("1010", "100100")

b)  For type SIG:

"0111" > "011100"

"0111" > "001111"

maximum("1010", "100100")

Exercise 9.9. Comparison operators (3)

Half of the assignments below are illegal; find them and explain why. (Hint: See table 9.5.)

a)  if bv > bv then …

b)  if slv ?> slv then …

c)  sl <= slv ?= slv;

d)  bo <= bv ?= bv;

e)  bo <= true when sig > nat else false;

f)  sfix <= sfix ?<= sfix;

g)  int <= 0 when suv < slv else 2**N;

h)  if flo > sfix then …

i)  bo <= '1' when int=pos else '0';

j)  sfix <= maximum(sfix, int);

Exercise 9.10. Shift and concatenation operators

a)  Which of these data types, BV, SLV, UNS, and SFIX, allow shift operations? (Hint: See table 9.6.)

b)  Say that x(5:0) = "110010". For each case below, write the value returned by the expression and an equivalent expression using the concatenation operator (the first line is already filled).

Exercise 9.11. Attributes of array type and objects

Consider the following declarations:

Determine the value returned by each expression (involving an attribute) below. (Hint: See section 9.3.2.)

a)  data_type'left

b)  data_type'right

c)  data_type'high

d)  data_type'length

e)  data'left

f)  data'right

g)  data'high

h)  data'range

i)  byte'left

j)  byte'right

k)  byte'length

l)  byte'ascending

Exercise 9.12. State machine encoding (1)

Consider the following declarations relative to the states of an FSM:

Write the codewords produced to represent the states using the encodings listed below (the first line is already filled).

a)  Sequential encoding → st1 = "000", st2 = "001", st3 = "010", st4 = "011", st5 = "100"

b)  Gray encoding

c)  Johnson encoding

d)  One-hot encoding

Exercise 9.13. State machine encoding (2)

Given the type and signal declarations of exercise 9.12, write an FSM encoding attribute for that exercise using:

a)  The synthesis attribute fsm_encoding, for one-hot encoding.

b)  The synthesis attribute syn_encoding, for gray encoding.

Exercise 9.14. Binary-to-BCD conversion

Binary-to-BCD (binary coded decimal) conversion, studied in section 1.4.5, consists in transforming an N-bit string, representing a decimal value, into several four-bit values, each representing one of the decimal digits. For example, 39 (= "100111") would become 3 (= "0011") and 9 (= "1001"). One of the arithmetic operators (section 9.1.2) can be used for that purpose. Find it and explain how it would be done. (Note: As seen in section 1.4.5, there are better ways of solving that problem, so this is just a practice with operators.)

10    Concurrent Code

A review of combinational circuits was seen in chapter 1. As we know, a circuit is said to be combinational when its output depends uniquely on its preset input, so the circuit has no clock or memory. Regular arithmetic circuits are examples of combinational circuits because the present computation (say, a multiplication) is not affected by previous computations. The material in that chapter will serve as basis for the examples and exercises in this chapter and in the next.

Purely concurrent VHDL code (i.e., without processes) is proper for implementing only combinational circuits, for which the statements when, select, and generate are commonly used. Digital systems, however, usually include sequential circuits, for which sequential code (and therefore sequential statements, like if, case, and loop) is needed. Concurrent code is studied in chapters 10 and 11, while sequential code (which can infer both sequential and combinational circuits) is studied in chapters 12 and 13.

It is also important to emphasize that many combinational circuits can be constructed without any formal statement thanks to the large collection of predefined operators (which are just function calls, as seen in chapter 9) and to the easiness with which data arrays can be constructed and manipulated in VHDL (as seen in chapter 8). As an example of the former, see example 6.1; for the latter, see example 8.1.

10.1    Concurrent Statements

In figure 6.1 we had a global view of a VHDL design code’s structure. The architecture body, being the region where the circuits are constructed, is naturally the most complex part, so it is in that region that we concentrate our efforts in this chapter and the next three.

Figure 10.1a shows what can be used to construct an architecture body. It can contain only concurrent statements (recall that VHDL is inherently concurrent rather than sequential), which are the following: when statements; select statements; generate statements; process statements; component instantiation statements; and procedure call statements (block and assertion statements were left out—the first, because of its rare usage; the second, because it is not used to generate hardware but rather to test it, so it will be seen in chapter 14). Because these statements are concurrent, their relative positions in the code do not matter.

Figure 10.1
(a) Concurrent and (b) sequential VHDL statements, all synthesizable (function call is not a statement but was included for clarity; see the text).

Note that function call was also included in the list of figure 11.1a. The reason for its being between parentheses is because function calls are not statements but rather part of expressions (chapter 14). For instance, the predefined operators (chapter 9) are function calls.

A special statement in this list is the process statement. Although as a whole it is concurrent with respect to all other statements, internally it is sequential, so only sequential statements (listed in figure 10.1b) can be used inside a process. Note that some statements appear in both (concurrent and sequential) lists.

The formal designations for the concurrent statements of figure 10.1a, according with the IEEE 1076 standard, are shown in table 10.1. Brief comments for each case follow.

Table 10.1

Main concurrent statements

Category/Subcategory

Statements

Studied in

Concurrent signal assignment statements

Conditional assignment

when

Section 10.2

Selected assignment

select

Section 10.3

Generate statements

for … generate

Section 10.4

if … generate

case … generate

Process statement

process

Section 12.3

Component instantiation statement

component instant.

Section 10.5

Concurrent procedure call statement

procedure call

Section 14.4

Concurrent signal assignment statements:    This category contains two very useful statements, called when and select. They are referred to as conditional signal assignment and selected signal assignment statements, respectively. Their original (concurrent) version is proper only for the construction of combinational circuits.

Generate statements:    As shown in table 10.1, there are three versions of generate. The most common is for-generate, which acts as a loop that replicates a number of times a section of code containing only concurrent statements. In summary, a loop is called generate in concurrent code (figure 10.1a) and loop in sequential code (figure 10.1b).

Process statement:    The only VHDL units that are not (internally) concurrent are process and subprograms (the latter consist of functions and procedures). However, of these, only processes are built directly in the statement part of an architecture body, and, as already mentioned, any process is concurrent with respect to all other statements as a whole. As will be seen in chapter 12, despite being sequential, a process can be used to construct both sequential and combinational circuits.

Component instantiation statement:    This statement allows previous designs (which can be combinational or sequential) to be included as part of a new design, hence allowing code reusability and IP inclusion. It also allows the construction of hierarchical (structural) code. Component instantiations are always concurrent, so they cannot be done in sequential units (process and subprograms).

Concurrent procedure call statement:    As already mentioned, VHDL subprograms consist of functions and procedures. However, while a function is called as part of an expression (for example, “if rising_edge(clk) then … ”, where rising_edge is a function, or “y <= a + b;”, where “+” is a function), a procedure call is a statement on its own (for example, “add(a, b, cin, sum, cout);”). Both (procedure and function) calls are allowed in concurrent (and sequential) code.

The main goal of this chapter is to describe and emphasize all that is needed to design combinational circuits using concurrent VHDL code. In the same way, chapter 12 will describe and emphasize all that is needed to design both combinational and sequential circuits using sequential VHDL code. This separation is important because, as seen in chapters 1 and 2, such circuits are analyzed and designed differently.

In summary, this chapter describes the following concurrent statements: when, select, generate, and component instantiation. The others (process and subprogram calls), since they are internally sequential, are studied separately, in chapters 12–13 and 14, respectively. Two special cases of concurrent code are also discussed in this chapter: (1) how to avoid assigning a value to a signal more than once and (2) how to implement arithmetic circuits properly in VHDL.

10.2    The when Statement

As seen in table 10.1, an assignment using the when statement is called a conditional assignment. Its syntax is presented below in two versions; note that the first ends with “else value,” while the second ends with “when condition.”

The target in the syntax above is a signal (VHDL-2008 allows when to be used also in sequential code, so there the target can be also a variable). The value can range from a simple static value up to elaborate expressions involving several values. Any number of tests is allowed, but only a few are usually employed (straightforward truth tables, which can be long, should be entered using the select statement as described in the next section). Indeed, note in the syntaxes above that the when statement has a priority-encoding nature (for any given line to be executed, the tests in all preceding lines must return false) and hence are definitely not tailored for entering straightforward truth tables (though that is not illegal).

A typical use for when is shown below (see the complete code in example 7.1). Note that it contains only one test, and the version ending in “else value” is employed.

Another example is shown below, using again the syntax ending in “else value.” The option on the left (VHDL-2008) is slightly less verbose, while that on the right is slightly clearer. Observe again the priority-encoding nature of when; for example, if a = '1', the output is "01", regardless of b, c, and d, so the second line is equivalent to outp = "10" if a ≠ '1' and b = '0'. Note also that it would take a substantial effort to explicitly describe all possible conditions for a, b, c, and d.

The advantage of ending with “else value” is that it guarantees complete input-output mapping coverage, so the compiler will not infer latches (explained shortly). On the other hand, the statement ending with “when condition” is more informative because all conditions are shown explicitly. In practice, explicit full-mapping descriptions are often not viable, particularly when using standard-logic types.

An example where explicit full description is viable is shown below and implements the multiplexer of figure 1.3a. The option on the left (ending with “when condition”) is obviously more informative than that on the right (ending in “else value”). (Recall, however, that because this is just a straightforward truth table, select is the recommended statement to implement it.)

The next example illustrates incomplete versus complete in-out mapping coverage. If the code on the left is employed, what should the output be, for example, when rst, hold, and run are all low? Since the compiler will execute anyway, it must make a decision that typically is to infer latches to hold the output’s current value. Such latches are highly undesirable because they add delays (which, by the way, are poorly predictable in FPGAs because latches are not built-in circuits), besides wasting hardware and power resources. Contrary to that, the code on the right provides a full in-out mapping description (it ends in “else value”), so latch inference does not occur.

Note: When the output of a truth table is registered (which can occur in sequential circuits), a full truth table description is not necessary (latches will not be inferred because the result is already stored in memory anyway).

The unaffected keyword (section 12.5) or, equivalently, the null statement can be used with when. However, in combinational circuits that causes the inference of latches, so neither should be employed in codes that are for synthesis.

10.3    The select Statement

As seen in table 10.1, an assignment using the select statement is called a selected assignment. Its main use is to enter truth tables, which select does better than when for two reasons: first, select does not posses the priority-encoding nature of when; second, full truth table coverage is checked automatically by the compiler, which is not running the compilation if the table is not completely described (hence preventing the inference of latches always).

The syntax for select statements is presented below, in two versions. Note that the first ends with “when others,” while the second ends with “when choice.”

The version on the left above employs the keyword others to cover all remaining cases, while the version on the right describes the entire truth table explicitly, leading to a more informative code. However, as mentioned previously, in most cases only the former is viable.

The target in the syntax above is a signal (VHDL-2008 allows select to be used also in sequential code, so there the target can be also a variable). The value can range from a simple static value up to elaborate expressions involving several values. In the choice expressions, the following can be used: to (for ascending index direction), downto (descending direction), “|” (interpreted as or), and the keyword others. Regarding the unaffected keyword or, equivalently, the null statement, see the comment at the end of section 10.2.

The example below shows an implementation for the multiplexer of figure 1.3a. Since in this case expressing all choices explicitly takes the same effort as using the others keyword, the code on the right, being more informative, is preferred.

The select? statement

The matching select statement (select?) employs the matching equality comparator (?=), which, as seen in section 9.1.3, assumes the following for std_ulogic values: '0'='L', '1'='H', and '−'=any value. Any other combination returns 'U' or 'X' or '0'. This statement is particularly useful when the truth table contains “don’t care” values at the input, as illustrated in the example below (for “don’t care” values at the output, see example 7.2).

Example 10.1. Priority encoder

The code below implements the priority encoder of figure 1.6.a. As usual, it starts with a packages list (lines 2–3, here with just one package), followed by the entity declaration (lines 5–9, using only type std_logic_vector for the circuit ports) and the architecture body (lines 11–19). Note the use of select? (lines 13–18) because “don’t care” inputs are involved. Observe also that the statement ends with “when others,” so all truth table entries are covered, allowing select? to be synthesized.

10.4    The generate Statement

As seen in table 10.1, there are three versions for this statement: for-generate, if-generate, and case-generate. They are described below.

for-generate:

This is the most frequently used form of generate. It acts as a loop, but because it is a concurrent statement, a piece of hardware is inferred every time the loop goes around (hence generate is indeed a well-chosen name).

An example is shown below, with two equivalent versions (that on the right uses the range and left attributes, seen in section 9.3.2, which help in the construction of parameterized code). Each time the generate loop goes around, an XOR gate is inferred, so the circuit of figure 10.2 is produced.

Figure 10.2
Hardware produced by a generate statement.

The hardware instantiated by the generate statement can be an entire design that we want to include as part (i.e., a component, in VHDL language) of a new, larger design. That will be seen in section 10.5.

The other two forms of generate are shown below.

These are conditional forms of generate. The syntaxes for if and case are similar to those for the sequential if and case statements (chapter 12). An application for these generate statements is to choose between different hardware specifications determined, for example, by a generic constant, as in the example below.

Example 10.2. Conditional adder instantiation

Say that we have a library of standard cells, among which are unsigned and signed adders of generic size. The unsigned version, called adder_unsigned, is shown in the first of the two codes below (to obtain the other version, just replace the word “unsigned” with “signed”). The number of bits is determined by the generic constant WIDTH (line 8), whose value can be left unspecified because it can/will be overwritten by the generic map association during instantiation.

The second code below is the main code, in which one of these adders is instantiated. The selection is made by a generic constant, called POLARITY (line 7), which points to the unsigned version when low (lines 18–19) or the signed version otherwise (lines 20–21). Note that the instantiation is done using the conditional case-generate statement (lines 17–22); another option would be to use the if-generate statement, but for simple cases the former is preferred. (Component instantiation details are presented in the next section.)

10.5    Component Instantiation Statements

As seen in figure 10.1, component instantiation statements are also concurrent statements, so they are not allowed in sequential units (i.e., process and subprograms). A single instantiation can be done directly, but looped instantiations require the generate statement, which, as we saw, plays the role of loop in concurrent code.

A previous design (i.e., a complete VHDL code) can be instantiated as part of another design in two equivalent ways: using a component instantiation or using a design entity instantiation. Both cases are described next.

10.5.1    Component Instantiation

To instantiate a component, a component declaration plus a component instantiation statement are needed, as shown below.

The component declaration (on the above left) consists simply of a copy of the entity declaration of the design to be instantiated with the word entity replaced with the word component. The component name is the same as its entity’s name. The typical location for this declaration is the declarative part of architecture, package, or generate statement.

The component instantiation statement (syntax on the above right) provides the (possibly new) names and values for the generic parameters by means of generic map, and the port mapping between the instantiated design and the current design by means of port map. The mapping associations can be named or positional; in the former, each association is explicitly named, while in the latter each position of the instantiated design is associated to the same position in the current design. Finally, if a port must be left unconnected in the instantiation, the keyword open should be used for it in the port map.

The example below shows, on the left, an entity called brick, of a design that is going to be instantiated by another design, whose entity, called wall, is shown on the right:

Corresponding component instantiations, labeled comp, are shown below:

Signal expressions in port map are allowed after VHDL-2008, as illustrated below:

A complete component instantiation will be presented in example 10.3.

10.5.2    Design Entity Instantiation

This is another way of instantiating one design as part of another. As shown in the syntax below, an advantage here is that the component declaration is not needed. The instantiated code is allowed to have multiple architectures because that of interest to the present design can be specified in the optional architecture_name field. The work.entity_name declaration assumes that the entity_name.vhd file is present in the project directory.

The instantiations below are again for the brick and wall circuits, so they can be compared to the previous component instantiations.

Example 10.3. Carry-ripple adder built with full-adder components

The full-adder unit and the carry-ripple adder were reviewed in sections 1.5.1 and 1.5.2, respectively. In this example we use the full adder of figure 1.12a to build the carry-ripple adder of figure 1.13b.

Solution 1: Using design entity instantiation

The first code below is for the full-adder unit, with the outputs calculated using boolean equations (lines 13–14). Note that std_logic_1164 is the only package needed in the packages list (lines 2–3). The second code is for the carry-ripple adder. In the entity declaration (lines 5–13), a generic constant called NUM_BITS (line 7) is used to specify the number of bits (which is also the number of full-adder units) in this circuit. In the architecture body (lines 15–24), a signal called carry is declared (line 16) to represent the internal circuit nodes (carry interface between the full-adder units). Next, a for-generate statement (lines 19–22) is used to do the instantiations (recall that a loop is called loop in sequential code, but it is called generate in concurrent code). Finally, observe that the instantiation (lines 20–21) was done using the design entity instantiation option (seen in this section), with positional association in the port map.

Solution 2: Using component instantiation

The architecture below shows the modifications needed in the code above to use the component instantiation option, studied in the previous section. It requires a component declaration (lines 17–21) plus a component instantiation statement (line 25). In this case, the component declaration (which is just a copy of the full-adder’s entity) is located in the declarative part of the architecture. Another option is to locate it in the declarative part of the generate statement (the keyword begin is then required), as shown in the subsequent code. Recall that still another popular place for component declarations is a package.

10.6    Avoiding Multiple Assignments to the Same Signal

This section and the next discuss two special cases related to concurrent code. The first (in this section) introduces a way to circumvent the fact that a signal cannot receive multiple assignments in concurrent code. The second (next section) shows recommendations regarding the implementation of arithmetic circuits in VHDL.

As we know, VHDL code is inherently concurrent, so any distribution of the statements (listed in figure 10.1) must lead to the same result. Consequently, we cannot assign a value to a signal somewhere in the code and then assign another value to it later, believing that the compiler should simply consider the last value as the valid one because that could lead to different circuits depending on the relative positions of the statements (that would be fine only in regions of sequential code, studied in chapters 12–13).

A solution when multiple assignments are necessary is to create an internal signal with an extra dimension (for example, with dimension 1D × 1D if the target signal is 1D—see figure 8.1) and use it to do the computations, passing then the last element value of that signal to the target signal. This approach is illustrated in the example below.

Note:    It will be shown in chapter 12 (example 12.11, process P1) that the technique above is not necessary when using sequential code and a variable to do the computations, which simplifies the solution.

Example 10.4. Hamming-weight calculator

The code below implements a circuit that determines the Hamming weight (HW) of a vector, which is the number of 1s in it. The input and output are inp_vector and hamm_weight, respectively.

Solution 1: As usual, the code starts with declarations regarding the packages needed in the design (lines 2–4); std_logic_1164 is needed because the type std_logic_vector is used in the circuit ports (lines 11–12), while numeric_std is needed because the type unsigned is employed for type conversion (line 23—see details about integer to std_logic_vector conversion in section 7.10.3).

The entity declaration (lines 6–13) starts with a generic list (lines 8–9), which allows the construction of a parameterized code. Note that if the minimum number of bits is wanted for BITS_OUT, then BITS_OUT is no longer an independent constant but rather a function of BITS_IN—that is, BITS_OUT = ⎡log₂(BITS_IN + 1)⎤ (see comments after the code). Next come the circuit ports (lines 11–12) using only the type std_logic_vector.

The architecture body (lines 15–24) starts with type and signal declarations (lines 16–17). This 1D × 1D signal (an array of integers) is needed because, to follow the strategy just described, a signal with an extra dimension with respect to the signal to be measured (an integer, hence 1D, according to figure 8.1) is needed. The statement part employs a loop (lines 20–22—recall that loops are produced by the generate statement in concurrent code) to produce the internal values, the last of which is passed to the output (line 23).

Solution 2: An option to improve the code above is to employ an expression in the generic list, as shown below, but check restrictions and other comments as noted in section 6.7.

Solution 3: The most formal solution is to list only the truly independent constants in the generic list, as shown below, leaving the log2 computation for the range specifications (hence with increased verbosity):

10.7    Suggested Approaches for Arithmetic Circuits

This is the second of the two special cases mentioned previously regarding concurrent code. It deals with arithmetic circuits, for which implementation suggestions are presented below.

Arithmetic circuits, reviewed in sections 1.5 and 1.6, are those for which sign matters. Therefore, the use of the type integer, for example, is not a good idea because the code then will not show explicitly whether the system is signed or unsigned (that is determined by the compiler upon inspecting the range specified for the involved integers).

The types recommended for arithmetic circuits, with the respective packages of origin (which must then be included in the code’s packages list—the second option for some of the packages below is for default parameters) are the following:

For integer arithmetic: unsigned or signed (package numeric_std).

For fixed-point arithmetic: ufixed or sfixed (package fixed_generic_pkg or fixed_pkg).

For floating-point arithmetic: float (package float_generic_pkg or float_pkg).

For conciseness, in this section we will refer to these types as “arithmetic” types.

It is important, however, to try to always use the same types for the interface signals (circuit ports) to allow direct connection between system blocks (in large projects) and help reusability. The standard-logic types (std_ulogic, std_logic, std_ulogic_vector, and std_logic_vector) are here considered the default types for that role. Only in particular cases should the arithmetic types be used directly in the circuit ports (for example, when it is a stand-alone design and reusability is not an issue or when a port type must be a user-defined type).

Suggested procedure

Before the VHDL code:

1)  Make sure that the circuit is arithmetic and decide which “arithmetic” type to use (see comment on integer versus floating point, presented next).

2)  List all arithmetic operations involved (as seen in section 9.1.2, the predefined arithmetic operators are +, −, *, /, **, rem, mod, and abs). Then check, using table 9.4, the constraints for each operator; for example, for types unsigned and signed, the “+” and “−” operators require the result to have the same number of bits as the largest operand, while the “*” operator requires the number of bits in the result to be equal to the sum of the bits in the operands.

3)  Decide how to deal with overflow (section 1.6.1) in case the constraints above might cause the circuit to be subject to overflow. If overflow is not acceptable, one solution is to extend the operands and the result (the resize function is in section 7.9.3); if it is acceptable but must be flagged, decide how that flag will be produced.

In the VHDL code:

1)  As a general rule, use only standard-logic types for the circuit ports (see possible exceptions listed above).

2)  In the architecture, convert the std_ulogic_vector or std_logic_vector inputs to one of the arithmetic types. Recall that single-bit standard-logic types (std_ulogic and std_logic) do not need any conversion.

3)  Do the computations.

4)  Convert the multi-bit results to standard-logic vector types and send them out (along with all single-bit standard-logic results, of course).

5)  Carefully simulate your design.

Examples of arithmetic circuits implemented using the suggestions above are presented after the following comment.

Integer (or fixed-point) versus floating-point    Floating point should be avoided whenever possible because of its high consumption of resources (hardware, primarily, but also some power; speed might also be impaired). One procedure that might help that decision is to define some error parameter for the target application and establish a maximum acceptable value for it and then run a corresponding analysis tool (Matlab, for example). Say that the analysis starts with 32-bit floating point, which passes the maximum-error test; the number of bits in the exponent and fraction should then be reduced gradually until the error limit is reached. This should then be repeated for integer (or fixed point) until the minimum number of bits is again obtained. Both should then be implemented in VHDL to check the amount of hardware consumed for the same device or technology in each case (plus other parameters, like maximum speed). As a practical example, this procedure could be applied to the sine calculator of example 11.3, for which the total harmonic distortion (THD) could be used as error parameter.

Example 10.5. Signed integer adder

Figure 10.3a shows a fully equipped N-bit signed integer adder, where a and b are the addends, cin and cout are the carry-in and carry-out bits, sum is the core result, and oflow is an overflow flag.

Figure 10.3
Signed adder of example 10.5.

Note that this adder contains all optional outputs (cout, oflow, and sum(N)), as seen in section 1.6.3; even though this kind of cell is usually not built with all three (see figure 1.21b), the purpose here is to practice with all possibilities.

The time behavior is illustrated in figure 10.3b, where the values of a and b are {−8, 0, 7, −8, 0, 7} and {−8, −7, 5}, respectively. It is left to the reader to complete it for later comparison against simulation results obtained after synthesizing the code that follows. (Signed integer addition was reviewed in section 1.6.3, and signed/unsigned types were studied in section 7.6.3).

Since this is an arithmetic circuit, we will follow the suggestions presented above. The right type is signed, and the only operator to be used is “+”. Consulting table 9.4, we observe in comment (8) that the size of the output must be the same as that of the largest input.

A VHDL solution is presented below. To make it clear, a step-by-step code is shown. The packages list (lines 2–4) contains the packages std_logic_1164 (because standard-logic types are employed for the circuit ports) and numeric_std (because type signed is used in the computations). In the entity declaration (lines 6–14), a generic constant (line 8; N is called NUM_BITS in the code) defines the number of bits in the multi-bit signals. Also, to make it clear that sum(N) is an optional output, it is called sumMSB (sum’s new most significant bit, if used) in the code.

The architecture body (lines 16–30) starts with the declaration of a signal called sum_sig (line 17), with NUM_BITS + 1 bits, which will be used to hold the sum temporarily. The sum (line 21) employs sign-extension and conversion to signed. The MSB of this extended sum (with NUM_BITS + 1 bits) is sumMSB (line 26), while all other bits constitute the original sum (line 25). Notice that the sum is converted to std_logic_vector before being sent out. Finally, the cout (line 27) and oflow (line 28) outputs are calculated using the equations of figure 1.22a.

Four additional observations about line 21 follow. First, an equivalent construction using the resize function (section 7.9.3) is shown in line 22. Second, table 9.4 tells us that a signed value can be added to another signed, integer, or std_ulogic value, so the fact that the sum includes a single-bit value is fine, but that was not so before VHDL-2008; if your compiler does not support that feature yet, a solution is to extend that bit by using ('0' & cin) or ('0', cin). The third observation is that only one of the inputs needs sign-extension because the output of addition for signed is required to have the same number of bits as the largest input. The final observation regards the number of adders: even though two sums appear in line 21, the compiler will understand that the last addend is just a carry bit, so a single adder with carry-in port will be inferred. Just out of curiosity, below is a solution using a single “+” operator, where an extra bit is appended at the right end of a and b, with cin in one addend and a '1' in the other (cin in both would also do). Observe below that sum_sig (line 17) has now NUM_BITS + 2 bits and that the sum’s LSB is ignored in all output equations.

Example 10.6. Floating-point adder and multiplier

This example shows the implementation of a floating-point (FP) adder/multiplier. Since this too is an arithmetic circuit, the suggestions presented above will again be followed. (FP arithmetic was reviewed in section 1.6.5 and FP types were seen in section 7.6.5.)

The data type now is float, and the operators involved in this circuit are “+” and “*”. Consulting table 9.4, we see in comment (28) that for both operators the output’s upper and lower bounds must be equal to the inputs’ largest and smallest bounds, respectively (though the compiler might require only the resulting vector length to be obeyed).

The first solution below employs the type float directly in the circuit ports (lines 7–8). The only package then needed in the packages list (lines 2–3) is float_pkg, which employs the default FP parameters (for example, the rounding style is roundTiesToEven, explained in section 1.6.5; as seen in section 7.6.5, the generic package is called float_generic_pkg). The inputs and outputs range is “5 downto −3” (lines 7–8), so the data representation is (S)(EEEEE)(FFF) for all, including the sum and multiplication (computed in lines 13–14). Because the exponent uses Ewidth = 5 bits, this circuit parameters are (see section 1.6.5) Emin = 1 (always), Emax = 30, MAX = 31, BIAS = 15, and 2⁻¹⁴ ≤ dec ≤ (2 − 2⁻³)2¹⁵.

In the second solution below, standard-logic types are employed for the circuit ports (lines 8–9), which are generally preferred. Note the inclusion of the std_logic_1164 package (line 3) in the packages list.

It is also important to mention that free compiler versions usually have less VHDL support than their paid counterparts, so FP support might be available only in the latter.

Simulation results for the adder part of the code above are shown in figure 10.4. The inputs are: a = (0)(11100)(010), so E = 28 and F = 1/4; b₁ = (0)(11101)(001), so E = 29 and F = 1/8; b₂ = (0)(11101)(100), so E = 29 and F = 1/2; and b₃ = (0)(11101)(110), so E = 29 and F = 3/4. BIAS is 15 in all cases. Note that these values are precisely those employed in the last example of section 1.6.5, from which the expected results, after truncation and rounding (using the roundTiesToEven style, which is the default and recommended style for FP arithmetic), are: sum₁ = a + b₁ = 1.110·2¹⁴ = (0)(11101)(110); sum₂ = a + b₂ = 1.000·2¹⁵ = (0)(11110)(000); and sum₃ = a + b₃ = 1.010·2¹⁵ = (0)(11110)(010). These values are in perfect agreement with the simulation results of figure 10.4.

Figure 10.4
Simulation results from the adder part of example 10.6.

10.8    Additional Examples and Exercises

These are in chapter 11, which is dedicated entirely to practicing with concurrent code. The list of all enumerated examples and exercises in this edition of the book is in appendix M.

11    Concurrent Code: Practice

This chapter is a continuation of chapter 10. Both deal with purely concurrent (i.e., without process) VHDL code, which is appropriate for implementing only combinational circuits. While the previous chapter was dedicated to more conceptual aspects (nevertheless with practical examples included), the present chapter is dedicated entirely to practice with concurrent code, including additional design examples and a list of select exercises.

The list of all enumerated examples and exercises in this edition of the book is in appendix M.

11.1    Additional Design Examples Using Concurrent Code

The following design examples using purely concurrent code are presented in this chapter:

Example 11.1. Vectors absolute difference calculator

Example 11.2. Programmable combinational delay line (structural)

Example 11.3. Sine calculator with integers and ROM-type memory

Example 11.1. Vectors absolute difference calculator

This example deals with a circuit that must calculate, in parallel and with minimum time complexity, the accumulated absolute difference abs_diff = Σ|ak − bk| between vectors a and b, where each vector contains M = 8 elements, the elements being N-bit signed integers (contrary to M, the parameter N is generic). Since this is an arithmetic circuit (sign matters), the suggestions presented in section 10.7 will be taken into consideration.

A diagram for this circuit is shown in figure 11.1a. The first layer computes the absolute difference between the vector elements. Note that the minimum number of bits at the output of this layer is N, not N + 1, which is clarified in figure 11.1b, where the subtraction and absolute-value functions were separated; while the former does require N + 1 bits, the second only requires N (for example, if the operators range from −16 to 15, the absolute difference can only go from 0 to 31, so both are five-bit values). Since resizing will be needed anyway, the final (used) approach is that depicted in figure 11.1c, which indicates that all data paths are resized to N + 3 bits (not N + 4). The remaining layers implement an adder array. Because minimum time complexity is required, the tree-type structure (figure 1.15b) was chosen, so the time complexity is O(L + 1), where L = ⎡log₂M⎤ (=3 for M = 8) is the number of layers in the adder tree.

Figure 11.1
Accumulated absolute difference calculator of example 11.1.

This is an arithmetic circuit that employs integers, so according to section 10.7, the right “arithmetic” type is unsigned or signed. In fact, both are needed in this example: signed for the first layer (because the vector elements can be negative) and unsigned after the absolute-value function. We must next check the constraints on the involved operators for these specific data types; table 9.4 shows that the “+” and “−” operators require the result to have the same number of bits as the largest operand, while the abs operator requires the output to have the size of the input. So now we finally have all information needed to develop a corresponding VHDL code.

A solution for this problem is presented next. Because an array of signed values is needed (to represent the vectors) at the circuit ports, and this kind of type is not available among the predefined types, it must be created in a package (the construction of packages is described in chapter 14), shown in the first code below. The package is called user_defined_type_pkg and the user-defined type is called signed_array, its individual elements being of type signed. Note that this package is listed in line 4 of the second code.

The design code is called abs_difference_calculator (line 6). The entity declaration (lines 6–12) starts with N being declared as a generic parameter (according with section 5.8.3, a better name would be NUM_BITS or WIDTH, for example; name N was kept just to match figure 11.1). Next come the circuit ports, where a and b are of type signed_array and the output is of type unsigned (with N + 3 bits).

The architecture body (lines 14–29), containing only purely concurrent code (no processes), has its statement region divided in two parts. The first (lines 20–22) implements the first layer of figure 11.1a, using the resize function (section 7.9.3) followed by subtraction, then the abs operator, and finally conversion to unsigned using type cast (section 7.10.2). The second part (lines 25–28) implements the adder array; because M is neither generic nor large, the tree was constructed explicitly (proper use of parentheses suffice to obtain the tree-type structure).

For fully generic trees, the use of sequential (instead of concurrent) code leads to simpler solutions, as illustrated in chapter 13 (example 13.1).

Example 11.2. Programmable combinational delay line (structural)

Tapped delay lines, described in section 2.6.9, are sequential circuits. The delay line treated here is their combinational counterpart (no clock involved). This kind of circuit is used, for example, in the construction of digital delay locked loop (DLL) circuits. This example offers a great opportunity for practicing with structural code (code that employs component instantiation) with complex structure (because the size of the circuit is generic and the sizes of its building blocks are not constant).

Figure 11.2a shows the circuit ports, where din is the (single-bit) signal to be delayed, sel is a control signal that selects the amount of delay, and dout is the delayed version of din. As shown in figure 11.2b, the size of each block is a power of two and the total number of blocks is M, hence producing a delay line that can operate with any number of delay units between 0 and 2M − 1 (a delay unit is a pair of inverters, as indicated in block 1).

Figure 11.2
Programmable combinational delay line of example 11.2.

Note: In ASICs, the switches of figure 11.2b are usually built with transmission gates (seen in figure 1.1), which exhibit very small propagation delay. If synthesized in an FPGA, the switches will likely be implemented as multiplexers (figure 11.2c), thus with non-negligible propagation delay (illustrated in the timing simulation ahead).

A solution for this problem is presented below using a structural approach. The first code is for a single block (called delay_block, line 6). It has a generic size (called BLOCK_LENGTH, line 8), whose value does not need to be specified here because that will be done during instantiation by the generic map association. All ports (lines 10–12) employ a standard-logic type. In the declarative part of the architecture body (lines 16–18), a signal called node_vector is created to represent the internal nodes, to which the keep attribute (section 9.5.3) is applied subsequently. Finally, in the statements part of the architecture body (lines 20–24), a generate statement (lines 21–23) is used to build a delay block with a total of BLOCK_LENGTH pairs of inverters. Note that the switch (mux) is implemented by a when statement (line 24).

The main code is called delay line (line 6 of the second code below). Note that M (called NUM_BLOCKS in the code, line 8) is a generic parameter and all ports (lines 10–13) employ again standard-logic types. In the declarative part of the architecture (lines 17–19), a signal is again created to represent the internal circuit nodes, to which the keep attribute is applied subsequently. Note that employing signal names already used in the previous code is fine because the codes are interpreted independently. In the statements part (lines 21–27) of the architecture body, a component (previous code) is instantiated NUM_BLOCKS times using a generate statement (lines 22–26). Recall that there are two ways of performing component instantiation; they are described in sections 10.5.1 and 10.5.2, with the latter being adopted here (lines 23–25). A final comment concerns line 11, which was replaced with line 12; both are fine, but entering sel in the simulation plots using decimal values is more practical than entering its binary values, which could cause some confusion because VHDL takes the leftmost bit as the most significant bit (MSB), so using a downto specification prevents that kind of problem.

Simulation results from this code are shown in figure 11.3, for NUM_BLOCKS = 4. The delay for sel = 0 is due mainly to the device’s in/out buffers plus the four multiplexers (switches). Note that, as expected, the delay grows as sel grows.

Figure 11.3
Simulation results from example 11.2.

Example 11.3. Sine calculator with integers and ROM-type memory

This example illustrates the use of integers (or, equivalently, fixed-point) instead of floating-point (to save hardware) and how read-only memory (ROM), which is just an array of constants, are implemented in VHDL. For that, the sine calculator of figure 11.4a is constructed; the circuit has phase (any integer in the 0-to-360 range) as input and sin(phase) (which is originally real valued) as output.

Figure 11.4
Sine calculator of example 11.3.

This design is based on two fundamental parameters: K (number of coefficients employed to represent one quadrant of the sine wave) and N (number of bits employed to represent each coefficient). Consequently, the ROM addresses will range from 0 to K − 1, each containing an N-bit word (note that K = 6 in figure 11.4b, so the addresses, shown between parentheses, go from 0 to 5). There are also two coefficient values not included in the ROM: for phase = 0°, 180°, or 360°, we use zero; for phase = 90° or 270°, we use the same value as that of the previous coefficient (i.e., ROM(K − 1) for 90° and −ROM(K − 1) for 270°). Even though sine coefficients can be obtained automatically (from Matlab, for example), their derivation is shown here explicitly to illustrate how it can be done and, especially, how normalization, addressing, and the use integers instead of floating-point can be implemented.

Say that, to determine the two parameters above, a procedure similar to that described in the comment on floating-point versus integer in section 10.7 was adopted, from which it was concluded that K = 32 integer coefficients of N = 7 bits suffice. Having these values, the sine coefficients can be calculated. As shown in figure 11.4b, they are given by sin(Δ/2), sin(3Δ/2), sin(5Δ/2), and so on, where Δ = 90/K. Since integers are going to be used, these coefficients must be rounded to the nearest integer in the 0-to-2N − 1 range; for example, for address 2, the coefficient is sin(5Δ/2) = sin(7.031 … °) = 0.1224 …, which, after multiplication by 127 and rounding, results in 16. In summary, the coefficients are determined by

coeff(addr) = (2N − 1)sin{[(2 ● addr + 1)Δ]/2} = (2N − 1)sin{[(2 ● addr + 1)45]/K},

where addr = 0, 1, 2, …, K − 1. Obviously, only the coefficients for one of the four sine function quadrants are needed. Such coefficients, for K = 32 and N = 7, are listed in the second column of table 11.1.

Table 11.1

Sine coefficients of example 11.4

The final step is to define how to deal with the input (phase) because usually there is no direct mapping between the input values and the ROM addresses. We can see in figure 11.4b that the mapping is 0 < phase < Δ → addr = 0, Δ ≤ phase < 2Δ → addr = 1, and so on. For example, for K = 6, any phase from 1° to 14° should access address 0, phase from 15° to 29° should access address 1, and so on. From this, we conclude that, given phase, the memory address to be accessed is

addr = phase/Δ = (K ● phase)/90

(the multiplication must come first to minimize the error). For K = 32, the corresponding phase intervals are those listed in the third column of table 11.1. For example, if phase = 46, the coefficient must be retrieved from addr = 16, resulting in sin(phase) = coeff(addr) = 92 (recall that the sine values are normalized to the ±(2N − 1) range).

A VHDL code for the circuit of figure 11.4a is presented below. Even though K and N (called NUM_COEFF and NUM_BITS, respectively, in lines 4–5 of the code) are not truly generic parameters (because when they change the sine coefficients must also change), they were entered as such to keep the code neat. Also for simplicity, integers were employed in the circuit ports (lines 7–8); it will be left to the reader to replace them with standard-logic types (as suggested in section 10.7).

The ROM memory is implemented by the array of constants in lines 13–17, while the circuit proper (ROM reader) is built with just a select statement in lines 19–29. Note that when the phase falls in the first quadrant (line 22), the equation for the ROM address is that just derived, while in the other three quadrants (lines 24, 26, and 28) phase adjustments are needed because the ROM coefficients are for the first quadrant only.

Corresponding timing simulation results are shown in figure 11.5.

Figure 11.5
Simulation results from example 11.3.

Note: The code would be simpler if the coefficients for all quadrants were included directly in the ROM. It is left to the reader to do that and compare the hardware consumption against the present solution.

11.2    Exercises

Notes regarding the exercises below:

1)  All circuits below are combinational (either logic or arithmetic), reviewed in chapter 1.

2)  In the exercises below, use only purely concurrent code (no process).

3)  For the circuit ports, use only standard-logic types (unless stated otherwise or unless a user-defined type is needed for the ports). If using generics, see section 6.7.

4)  For arithmetic circuits, observe the recommendations of section 10.7.

5)  For simulations, check appendix D. Only one graph is allowed (unless stated otherwise), so choose the stimuli carefully.

6)  The resources (hardware) usage is reported by the compiler after synthesis. It can be given in lookup tables (LUTs), logic elements (LEs), adaptive logic modules (ALMs), and so forth. The number of flip-flops is usually listed as FF or Number of Registers.

7)  Check the naming suggestions in section 5.8 (particularly, sections 5.8.1 to 5.8.4).

8)  For seven-segment displays (SSDs), check appendix E.

9)  The symbols commonly used by VHDL compilers to describe the inferred boolean equations are ! (NOT), & (AND), # (OR), and $ (XOR).

Part 1: Combinational Logic Circuits

Exercise 11.1. Multiplexer (1)

Each assignment below will produce a multiplexer-like circuit, the first using the when statement, the second using the select statement (recall that the latter is more appropriate for entering truth tables). The inputs are x0, x1, and x2; the output is y; and the selection port is formed by two single-bit signals called a and b. Draw the truth table for each code and determine whether the implemented circuits are equivalent.

Exercise 11.2. Multiplexer (2)

We saw in example 8.1 the implementation of a totally generic multiplexer for which a user-defined type was employed. Redo it using now only predefined types (hence with no need for a user-defined package). More specifically, use only standard-logic types for the circuit ports. Enter NUM_INPUTS and NUM_BITS as generic constants. (Hint: Sections 7.6.6 and 7.10.3.)

Exercise 11.3. Address decoder

Address decoders were reviewed in section 1.4.2.

a)  Why are they said to be combinational?

b)  Write a VHDL code that implements the circuit of figure 1.4. Enter the number of bits at the input (call it NUM_BITS) as a generic parameter. Observe notes 2 and 3 above. (Hint: Section 7.10.3.)

c)  Show simulation results, for NUM_BITS = 3. Observe note 5 above.

Exercise 11.4 Leading-ones counter

The circuit in figure 11.6 must count the number of ones in vector x, from left to right, before the first zero is found, with the result sent out in binary form (y) and also exhibited on an SSD (see note 8 above).

Figure 11.6
Circuit of exercise 11.4.

a)  In this case (single SSD), what is Nmax and thus the number of bits in signals y and ssd?

b)  Does this circuit require a clock? Why is it said to be combinational?

c)  Complete the time diagram included in the figure, where N = 8. Here, express y using unsigned decimal values.

d)  Solution 1: Write a VHDL code for this circuit. Enter N and M (call them BITS_IN and BITS_OUT, respectively, in the code) as generic constants. Note that M is not an independent constant, so follow the suggestion proposed at the end of section 6.7. Observe also notes 2 and 3 above. (Hint: Example 10.4.)

e)  Solution 2: Adjust the code above using only N (BITS_IN) as generic constant. You may declare BITS_OUT as an internal constant.

f)  Show simulation results, for N = 8 and 80ns time slots. Use the same stimuli of figure 11.6 and decimal values (in the plots) for y.

g)  Demonstrate your design in the FPGA board. Use switches or some other arrangement to produce the input vector and test it with the same stimuli of figure 11.6 (compare the results).

Exercise 11.5. Largest cluster of ones

Assume now that the circuit of figure 11.6 must inform the size of the largest group of consecutive ones instead of informing the number of leading ones; for example, if the input is x = "0011010111100", the output has to be y = 4. Solve all parts (a to g) of exercise 11.4 for this new circuit.

Exercise 11.6. Circuit with “don’t care” outputs

Figure 11.7 shows the ports of a combinational circuit that must produce the function described by the accompanying truth table. Note that some output values are marked as “don’t care.”

Figure 11.7
Circuit of exercise 11.6.

a)  Using Karnaugh maps, derive the optimal boolean equations for both output bits (b₁ and b₀). (Hint: See example 7.2.)

b)  Implement this circuit using VHDL. Recall that the select statement is the right choice for entering truth tables in concurrent code (its equivalent in sequential code is the case statement). Observe notes 2 and 3 above.

c)  Carefully check and write down the equations produced by the compiler (see note 9 above). Are they optimal (i.e., same as those derived in part (a))?

Exercise 11.7. Binary-to-BCD converters

Binary-to-BCD converters were studied in section 1.4.5, where two approaches were presented: with dividers (figure 1.8) and with the double-dabble algorithm (figures 1.9 and 1.10). The solutions below should be for twelve bits at the input, so the codes do not need to be generic. (Hint: After solving this exercise, check example 14.5.)

a)  Write a VHDL code that implements the option with dividers, with or without the rem or mod operator. By the way, if using one of these, which one is the right choice? Observe notes 2 and 3 above. After compilation, write down the amount of resources consumed to build this circuit (see note 6 above).

b)  Show simulation results for bin = {0, 1, 999, 1000, 4095}, each lasting 80ns.

c)  Repeat part (a) for the option with the double-dabble algorithm (figure 1.10b).

d)  Repeat part (b) for this new solution.

e)  Compare the resources usage of the two solutions above.

f)  Demonstrate your design from part (c) in the FPGA board. Explain how you are going to produce the input signal.

Exercise 11.8. Ring oscillator

Figure 11.8 shows a ring oscillator, which has also an oscillator-enable port. The number of inverters inside the loop has to be even because one of the inversions is produced by the NAND gate.

Figure 11.8
Ring oscillator of exercise 11.8.

a)  Implement this circuit using VHDL. Enter the number of inverters (call it NUM_INV) as a generic constant. Observe notes 2 and 3 above.

b)  Show the RTL view produced by the compiler for NUM_INV = 6.

c)  Show simulation results for NUM_INV = 6. What is, approximately, the frequency of oscillation?

Part 2: Combinational Arithmetic Circuits

Exercise 11.9. Unsigned and signed integer multipliers

Figure 11.9 shows a multiplier for unsigned integers in (a) and for signed integers in (b), with WIDTH bits for the inputs and 2●WIDTH bits for the output.

Figure 11.9
Multipliers of exercise 11.9.

a)  According to section 10.7, which are the right “arithmetic” types for implementing these circuits? Recall that, for the circuit ports, the default types are the standard-logic types.

b)  Complete the plot for prod in both figures.

c)  Implement both multipliers in the same VHDL code (call the outputs prod_uns and prod_sig). Enter WIDTH as a generic parameter and observe notes 2 to 4 above.

d)  Show simulation results using the same stimuli of figure 11.9, with only binary values and 80ns for the smaller time slots. Compare then prod_uns and prod_sig to the results from part (b).

e)  Finally, using the same stimuli but only decimal (instead of binary) values for all signals, show a graph with the simulation results for figure 11.9a only (i.e., without the plot for prod_sig) and another graph for figure 11.9b only (without the prod_uns plot), then comment on the correctness of the results.

Exercise 11.10. Unsigned and signed integer dividers

Figure 11.10 shows a divider for unsigned integers in (a) and for signed integers in (b), with WIDTH bits for all signals..

Figure 11.10
Dividers of exercise 11.10.

a)  According to section 10.7, which are the right “arithmetic” types for implementing these circuits? Recall that, for the circuit ports, the default types are the standard-logic types.

b)  Complete the plots for quo (quotient) and re (remainder) in both figures.

c)  Implement both dividers in the same VHDL code (call the outputs quo_uns, re_uns, quo_sig, and re_sig). Enter WIDTH as a generic parameter and observe notes 2 to 4 above. Check for invalid denominator is optional.

d)  Show simulation results using the same stimuli of figure 11.10, with only binary values and 80ns for the smaller time slots. Compare then the four results to those from part (b).

e)  Finally, using the same stimuli but only decimal (instead of binary) values for all signals, show a graph with the simulation results for figure 11.10a only (i.e., without the plots for quo_sig and re_sig) and another graph for figure 11.10b only (without the quo_uns and re_uns plots), and comment on the correctness of the results.

Exercise 11.11. Overflow analysis in division of integers

Say that the number of bits in a and b in the dividers of figure 11.10 are N and M, respectively. What are the minimum numbers of bits in quo and re such that overflow never occurs in the following cases:

a)  Divider of figure 11.10a, with N > M (for example, 6 and 4).

b)  Same divider, now with N < M (for example, 4 and 6).

c)  Divider of figure 11.10b, with N > M.

d)  Same divider, now with N < M.

Exercise 11.12. Division of integers with rounding

The circuit in this exercise must compute the division quo = a/b, where a, b, and quo (quotient) are all unsigned integers with WIDTH bits. The output (quo) must be rounded up or down properly.

a)  According with section 10.7, which is the right “arithmetic” type for implementing this circuit? For the circuit ports, use one of the default (standard-logic) types.

b)  Implement the circuit using VHDL. Enter WIDTH as a generic parameter. (Hint about rounding: section 7.6.6, but be aware of overflow.)

c)  Show simulation results, for WIDTH = 8. Observe note 5 above.

Exercise 11.13. Floating-point adder and multiplier

An FP adder plus multiplier was seen in example 10.6. Compile the code for the second solution and present simulation results using stimuli different from those in figure 10.4. Include an analysis of the results (as in example 10.6).

Exercise 11.14. Floating-point subtracter and divider

a)  An FP adder plus multiplier was seen in example 10.6. Develop a subtracter plus divider counterpart, using the same range (5 downto −3) for all signals. Observe notes 3 and 4 above.

b)  Show simulation results using the same stimuli of example 10.6. Show also a detailed analysis of the results, as done in that example.

Exercise 11.15. Generic chain-type adder array

The goal of this exercise is to design the chain-type adder array of figure 1.15a, assuming that the inputs are N-bit signed integers (thus ranging from −2N ⁻¹ to 2N ⁻¹ − 1) and the total number of inputs is M.

a)  Implement this circuit using VHDL. The code must be truly generic (i.e., with M and N, but nothing else, in the generic list). Use the type signed (and/or a user-defined type based on it) for both the circuit ports and the internal computations.

b)  Show the register transfer level (RTL) view produced by the compiler, for M = 8 and N = 4. Does it resemble figure 1.15a?

c)  Present simulation results, for M = 5 and N = 4, with 80ns test intervals.

Exercise 11.16. Generic tree-type adder array

This exercise is a more advanced version of exercise 11.15 because here the tree-type structure (figure 1.15b) must be constructed. Additionally, solving this problem with concurrent code is more difficult than with sequential code (see example 13.1).

a)  Implement it using VHDL. The code must be truly generic (i.e., with M and N, but nothing else, in the generic list). Use the type signed (and/or a user-defined type based on it) for both the circuit ports and the internal computations.

b)  Show the RTL view produced by the compiler for M = 8 and N = 4. Does it resemble figure 1.15b?

c)  Present simulation results for M = 5 and N = 4, with 80ns test intervals.

Exercise 11.17. Multiple detector

The circuit in this exercise must detect whether a signal a is a multiple of a signal b, both being non-negative integers. The outputs should be two flags; the first is called is_multiple, asserted when a is a multiple of b, and the second, invalid_input, asserted when any input is zero.

a)  Draw this circuit’s top-level diagram (just a box with the circuit ports, as in figure 11.10a).

b)  Is this circuit combinational or sequential? Why?

c)  Implement it using VHDL. Enter the number of bits (WIDTH) in a and b as a generic parameter, and observe notes 2 to 4 above.

d)  Show simulation results, for WIDTH = 5. See note 5 above.

Exercise 11.18. Square-root calculator

The square root of a number can be calculated using a combinational or sequential circuit. The former (treated here) is faster and probably consumes less power, while the latter (treated in example 13.4) requires less hardware. The purpose of this exercise is to calculate y = ⎣√x⎦, where x is the input and y is the output (both of type natural).

a)  Choose/study a combinational square root extraction algorithm to solve this problem (for example, one of the versions of the nonrestoring square root algorithm). Briefly explain its operation.

b)  Implement it using VHDL. Enter the number of bits (NUM_BITS) in x as a generic parameter.

c)  Check the amount of hardware used by the compiler to build your solution (see note 6 above) and compare it to that in example 13.4.

d)  Show simulation results for NUM_BITS = 8.

Part 3: With Component Instantiation (Structural Code)

This will be seen in chapter 13 (exercises 13.39 to 13.44).

12    Sequential Code

A review of sequential circuits was seen in chapter 2. As we know, a circuit is said to be sequential when its output depends on previous system states, so memory is needed. Except for rare cases (asynchronous-logic based systems, for example), sequential circuits are clocked. The material in chapter 2 will serve as basis for the examples and exercises in this chapter and in the next.

While purely concurrent VHDL code (i.e., without processes, studied in chapters 10–11) is proper for implementing only combinational circuits, sequential code is fine for both sequential and combinational circuits. This chapter is dedicated to the more conceptual aspects of sequential code, while chapter 13 is dedicated entirely to the practice of circuit design using sequential code.

12.1    Concurrent Code versus Sequential Code

Figure 12.1 (adapted from figure 10.1) shows the statements (all concurrent) that can be used to construct an architecture body (see comment on exceptions in section 10.1). The first four (when, select, generate, and component instantiation statements) were already studied in chapters 10–11, which are dedicated to concurrent code. The next statement in figure 12.1 is process, which is internally sequential (a mini program), thus referred to as a region of sequential code (recall, however, that as a whole a process is still concurrent with respect to all other statements). Its construction, which is very different from concurrent code, is studied in this chapter and is further practiced within the next. Sequential code is needed to build sequential circuits, but it allows the construction of combinational circuits as well.

Figure 12.1
(a) Main statements allowed in an architecture body (they are all concurrent); (b) Statements allowed in a process or subprogram, which are internally sequential.

Besides process, there are two other constructs in VHDL that are internally sequential, called function and procedure—collectively referred to as subprograms. However, because these are not constructed in the code proper (they can be used in the architecture’s statement part, but they must be constructed elsewhere, like a package or the architecture’s declarative part), they are treated separately, in chapter 14. Note in figure 12.1 that procedure and function calls are both allowed in concurrent and sequential code (the latter is shown between parentheses because a function call is not a statement but rather part of an expression).

As a final comment, observe in figure 12.1b the presence of the when and select statements (studied in chapter 10) inside the process statement. VDHL-2008 introduced sequential versions for them, so now they can be used in both concurrent and sequential codes.

12.2    Detecting Clock Transitions: clk'event or rising_edge(clk)?

Because clock is part of basically all sequential circuits, clock-edge detection (for the inference of registers) is an indispensable test. Therefore, before we begin, it is important to know how such tests can be done in VHDL.

A clock transition can be detected using the event attribute (section 9.3.3) directly, as shown below:

The detection can also be done indirectly using the rising_edge or falling_edge function, which is constructed using the event attribute. The following tests then result:

The rising_edge and falling_edge functions employ the To_X01 function (section 7.10.4) to convert 'L' to '0', 'H' to '1', and all other values to 'X'. Next, it checks the logic level on both sides of a transition, being the transition considered valid only if one side is '0' (or 'L', because if it existed it was converted to '0') and the other is '1' (or 'H', for a similar reason) or vice versa.

Even though both approaches above are fine for synthesis (because only the values '0', '1', 'Z', and '−' would be used in a properly developed design, so the clock level would never be anything but '0' or '1'), the latter (rising_edge and falling_edge functions) is preferred because it takes into account all std_ulogic values, which can be relevant for simulation or for synthesis of designs that employ the (nonrecommended) logic levels 'L' and 'H'.

Besides std_ulogic (and therefore also its subtype, std_logic), these two functions are predefined also for types bit and boolean.

12.3    The process Statement

As mentioned, a process as a whole is a concurrent statement, but internally it is a region of sequential code. Therefore, only sequential statements (if, case, loop, and wait, plus the sequential versions of when and select) are allowed inside it (figure 12.1b). Procedure and function calls are also allowed (though the latter, as already mentioned, is not a statement but rather part of an expression).

A process can be constructed as shown below:

The label in the syntax above is optional, having the usual purpose of improving readability.

The sensitivity list is responsible for waking the process, causing it to execute, which occurs when any signal in the list changes. Once started, the process runs to completion, which occurs when it reaches the end process keywords. (The actual suspension mechanism is an implicit wait statement located just before the end process keywords.)

If an explicit wait statement is employed in the process, the sensitivity list is not allowed. The wait statement is then responsible for waking the process, causing it to execute when the condition associated with that statement is fulfilled. Once started, the process again runs to completion, which now occurs when the process (after going around) returns to the explicit wait statement. A very important point here is that the process suspends even if the condition is still true.

The process’s declarative part, which is optional, can contain declarations of type, subtype, constant, variable, file, alias, attribute (plus attribute specification), group, and group template. It can also contain full subprograms, full packages, and use clauses. Signal declarations are not allowed, while variable declarations are by far the most common.

Finally, the statement part of a process, which is where the circuit or a portion of it is actually built, can contain only sequential statements and subprogram calls, as listed in figure 12.1b.

Example 12.1. Flip-flop inference with and without sensitivity list

Process P1 below is one of the most common processes; it infers simply a register formed by positive-edge flip-flops with asynchronous reset. Note that it has a sensitivity list, with clk and rst, and it employs the sequential statement if to describe the behavior of the desired circuit. If rst goes to '1', the output is zeroed regardless of the clock; otherwise, if a positive clock transition occurs, the input (d) is copied to the output (q). Since D-type flip-flops (DFFs) are precisely circuits that store data directly at clock transitions, the compiler will understand that DFFs are needed.

Process P2 is equivalent to P1 (here without reset), but now there is no sensitivity list, so an explicit wait statement must be provided. When a positive clock transition occurs, the wait condition is satisfied, so the process executes. As mentioned, the process always suspends when it reencounters the wait statement, even if the condition is true; in other words, it only executes again when a new rising clock edge occurs, so the first wait statement can be replaced with the second, which is less verbose (though less informative).

Example 12.2. Consequences of incomplete sensitivity lists

Two cases are illustrated in figure 12.2. The first (figure 12.2a) is just a DFF and hence one of the simplest sequential circuits, and the second (figure 12.2c) is just an AND gate and hence one of the simplest combinational circuits. It will be shown that even in such simple cases the consequences of incomplete sensitivity lists can be fatal.

Figure 12.2
Consequences of incomplete sensitivity lists.

Say that we want to implement the circuit of figure 12.2a, for which processes P1 and P2 below are written:

Process P1 is similar to P1 of example 12.1, so we know that the right circuit (figure 12.2a) will be inferred. Process P2, however, does not contain rst in its sensitivity list, so P2 does not execute when rst changes. Consequently, this reset is synchronous (we would call it clear rather than reset in the context of this book) because it can only be effective when the clock ticks. The equivalent circuit is then that of figure 12.2b (seen previously in figure 2.6b) and hence is substantially different from the intended implementation.

As a side comment, note that din, in spite of being one of the circuit inputs, was not included in the sensitivity list of P1. That is fine because the value of din is needed only when the clock ticks, so its presence in the list is irrelevant. This can be confirmed by observing that the process cannot execute when din changes because that will be blocked by the if statement (either by the if or elsif clause).

Say that now we want to implement the circuit of figure 12.2c, for which processes P3 and P4 below are written:

Because of the all keyword, the sensitivity list of P3 covers all inputs (a and b), so the compiler can implement the circuit unambiguously. The list in P4, however, covers only signal a. This means that the output should only change when a changes, leading to the inference of memory and resulting in the circuit of figure 12.2d (or equivalent), which is completely different from the intended target.

Notes:

1)  Even though in both cases above the compiler might issue a warning and then proceed as if all inputs had been included in the sensitivity list, such action is neither guaranteed not standardized, so a well-written code should always have complete control over the synthesis process. In summary, an incorrectly filled sensitivity list should always be treated as a serious mistake.

2)  It is recommended to use the keyword all for all combinational circuits. For sequential circuits, all can again be used; but, when only signals like clock and reset are involved, it is a common practice to list these explicitly.

12.4    The if Statement

We describe now the sequential VHDL statements, starting with if; this is the sequential counterpart of the concurrent when statement, studied in section 10.2 (though now when can be used also in sequential code). Its syntax is shown below.

The sequential statement parts in the syntax above can contain assignment expressions using any of the options listed in figure 12.1b. Any number of elsif clauses is allowed, but if there are many, the if statement is probably being used to describe a straightforward truth table, for which the case statement (described next) is recommended.

Note in the syntax above that, like when, if too has a priority-encoding nature (for any given line to be executed, the tests in all preceding lines must return false), so it is not tailored for entering straightforward truth tables (though that is allowed).

When an assignment using if is not registered (hence implementing a combinational circuit), the truth table that describes its in-out mapping must cover all possible conditions; otherwise, latches will be inferred (which impair the time behavior, besides wasting resources), as previously described for the concurrent when statement in section 10.2.

On the other hand, if the if statement is registered (under if rising_edge(clk) … , for example), the truth table does not need to be complete because, when an unspecified condition is found, the register (DFFs) will simply hold its current value automatically.

The reserved word unaffected (section 12.5) or the null statement—both indicating that no action should take place—are allowed to be used with the if statement, so the three codes below are equivalent. The interesting point here is that for all unspecified conditions the register would remain unchanged anyway (as described above), so here unaffected and null would have just an informative role.

An application where if is used often is in the construction of counters, as illustrated in the example below.

Example 12.3. Counter with is_max flag and RTL analysis

The circuit of figure 12.3a must count from 0 to MAX, keeping the is_max flag high while count = MAX, as illustrated in figure 12.3b for MAX = 6 (propagation delays were included to portray a more realistic situation).

Figure 12.3
Counter with is_max flag.

We start by sketching an RTL diagram for the expected circuit (notice that this problem is quite similar to that in figure 2.19e). The simplest solution is to build the circuit in two parts (counter and flag generator), as depicted in figure 12.4, where the counter can be either one from figure 2.11. Note in figure 12.4a that the comparator at the output of the counter looks at MAX (= 6), so when the clock ticks again the counter is (synchronously) returned to zero (this can be understood by observing the gray shades in figure 12.3b). Because the signal called clear is high while i = 6, this signal could be used directly as flag if is_max were not to be registered; however, having chosen to register it, the comparator in the flag generator has to check for MAX − 1 (= 5), thus causing the flag to be raised when the counter enters the MAX state.

Figure 12.4
A solution for example 12.3. (See optimal solution in exercise 13.8.)

The disadvantage of splitting the problem into subcircuits is that we might miss the opportunity to implement a simpler solution. (For instance, could the signal is_max be useful to the counter?) Such a case is treated in exercise 13.8.

A corresponding VHDL code is shown below. Because the target circuit is sequential, sequential code is needed (process of lines 20–33), with the circuit described using two if statements. The inner if (lines 23–31) describes the circuit behavior, while the outer if (opened in line 22 and closed in line 32) causes the output of the inner if to be registered (i.e., stored in DFFs); in other words, signals i and is_max are registered. Note that the value of i is passed eventually to count (line 34), so these two signals are indeed the same (i was used only because dealing with integers in the implementation of counters is simpler than dealing with standard-logic types). Note also that BITS (line 9) is not an independent constant, having been employed as a generic constant for clarity (see discussion on generics in section 6.7); if line 9 is removed, BITS in lines 12 and 34 can be replaced with an expression that calculates the value of BITS based on the value of MAX (also explained in section 6.7).

Note that the process just described (P1) was based on figure 12.3b, which has the advantage of not requiring major understanding about the underlying hardware. The second solution, in process P2, was based on figure 12.4 instead, having the advantage of being clearer because the two subcircuits were “untangled.” The optimal solution is that treated in exercise 13.8, but it requires a greater involvement with the hardware.

Solutions using a variable (recommended) instead of a signal to implement the counter will be seen in example 12.6.

The RTL view produced by the compiler is shown in figure 12.5a. As expected, two comparators (pointed to by fat arrows) were inferred. The number of registers (DFFs) is also as expected: three for the counter (0 to 6) plus one for the flag. Recall that the circuit actually implemented by the compiler might differ slightly from the RTL view, which depends on the target device (though that is not the case here). Corresponding timing simulation results are shown in figure 12.5b.

Figure 12.5
(a) RTL view produced by the compiler and (b) simulation results from the code of example 12.3.

12.5    The case Statement

The case statement is the sequential counterpart of the concurrent select statement (though now select can be used also in sequential code). Its main use is to enter straightforward truth tables, which case does better than if, for two reasons: first, case does not posses the priority-encoding nature of if; second, full truth table coverage is checked automatically by the compiler, not running the compilation if the table is not complete (thus preventing the inference of latches always).

The syntax for case statements is presented below in two versions. Note that the first ends with “when others,” while the second ends with “when choice.”

The syntax on the left above uses the keyword others to cover all remaining cases, which eases the description of truth tables, particularly when standard-logic types are employed. The version on the right describes the entire truth table explicitly, leading to a more informative code, though in most practical cases this option is not viable.

Similarly to the select statement, the following can be used in the choice expressions for the case statement: to (for ascending index direction), downto (descending direction), “|” (interpreted as or), plus the keyword others (already seen in the syntax above).

The example below shows two implementations for the multiplexer of figure 1.3a (note that here we are implementing a combinational circuit using sequential code). The effort to express all choices explicitly is the same in both statements, but observe that the statement on the right is more informative.

In contrast, the next truth table is more difficult to cover explicitly, particularly if the values are of standard-logic type, so others is employed.

The unaffected keyword

The reserved word unaffected (or the null statement) can be used to indicate explicitly that no action should take place. It is mainly intended to fill the case statement choices because it requires full truth table coverage to be compiled.

This is illustrated from an RTL perspective in the processes below by using a multiplexer. P1 is registered (note the if rising_edge(clk) statement), while P2 is not (thus, in principle, P2 is a purely combinational circuit). In both, the inputs are a and b, the output is y, and the selection port, sel, is a two-bit integer ranging from 0 to 3. P1 clearly implements the registered multiplexer of figure 12.6a, where, to keep y unchanged for sel = 2 or 3, y is connected back to the multiplexer (so y <= unaffected could be replaced with y <= y). P2 does not have any memory, so a latch is inferred to hold the value of y while sel = 2 or 3, resulting in the implementation of figure 12.6b (or equivalent), which includes a latch (the latch’s ena input corresponds to the clk input in figure 2.1a). Consequently, for synthesis, the use of unaffected should be restricted to registered assignments.

Figure 12.6
Circuits inferred by (a) registered and (b) unregistered assignments using the reserved word unaffected.

The case? statement

Like the matching select statement (select?, section 10.3), the matching case statement (case?) employs the matching equality comparator (?=), which, as seen in section 9.1.3, assumes the following for std_ulogic values: '0'='L', '1'='H', and '−'= any value. Any other combination returns 'U' or 'X' or '0'. This statement is particularly useful when the truth table contains “don’t care” values at the input, as illustrated in the process below, which implements the same priority encoder of example 11.1. (For “don’t care” values at the output, see example 7.2.)

12.6    The wait Statement

The wait statement is another sequential statement, so it can only be used in sequential code. Recall that when an explicit wait is included in a process, the sensitivity list must be omitted.

It is available in four forms: wait (unconditional), wait until, wait on, and wait for (the first and last are for simulation). The syntaxes for the conditional cases are shown below.

The three versions of wait above are illustrated in the processes below (note the absence of sensitivity lists). The first infers a negative-edge DFF with synchronous clear; the second infers an adder (a combinational circuit); and the third produces a clock waveform with a period of 20ns for use in simulation with VHDL testbenches (chapter 18).

The use of wait for synthesis is of modest help (it can reduce the verbosity slightly in some cases, but its low flexibility would lead to awkward codes in many situations), so one might leave it just for simulation (chapter 18), where it can indeed play a major role.

12.7    The loop Statement

As the name says, loop is used when a section of code must be repeated several times. It is the counterpart of the concurrent statement generate (section 10.4). Being another sequential statement, loop can only be used inside processes and subprograms.

There are five versions for the loop statement: unconditional loop, while-loop, for-loop, loop with exit, and loop with next. Because the exit and next statements are intended for use with loop, we do not treat them separately but rather together with loop, which is helpful from a learning perspective in this section. The for-loop option is by far the most frequently used of them for synthesis. All syntaxes are shown below, where the range (when required) must have static bounds.

unconditional loop:

while-loop:

for-loop:

loop with exit:

loop with next:

Example of for-loop:

The loop below implements an inverted-range nand operation between vectors a and b.

Example of loop with exit:

The loop below counts the number of leading ones, from left to right, in a vector called data.

Example of loop with next:

The loop below counts the total number of ones (Hamming weight) in a vector called data.

As seen in the examples above, the use of loop is straightforward. However, there is a special case, which must be observed carefully: it is the case of combinational loops. They can occur when using the loop statement to implement a combinational circuit (described later, in section 12.12).

12.8    The Sequential when and select Statements

As already mentioned, VHDL-2008 added sequential versions of when and select statements, which originally had only the concurrent versions (chapter 10). Consequently, they can now be used in sequential code. Their syntaxes and properties are the same seen in sections 10.2 and 10.3.

An example is shown below using the when statement together with if (left) and wait (right) statements. These are probably the most compact codes to implement DFFs with synchronous reset (clear).

12.9    Signal versus Variable

The syntax and general properties of signals and variables were described in sections 6.6.2 and 6.6.3, respectively. It is important that the reader review them before proceeding.

To help understand the properties and usage of signals and variables, their main differences are summarized by six “rules,” described below and also listed in table 12.1.

Table 12.1

Signal versus variable

Rule/Feature

Signal

Variable (*)

1

Places of declaration

In entity, architecture, package, block, and generate (forbidden in sequential units)

Only in sequential units (i.e., process and subprograms)

2

Scope

Global (can be read and modified anywhere in the architecture statements region)

Local (can be read and modified only inside its own sequential unit)

3

Assignment symbol

<= (example: sig <= 255;)

:= (example: var:= 255;)

4

Multiple assignments

Allowed only inside sequential units, but be aware of combinational loops

Always fine (because they can only be used inside sequential units anyway)

5

Update

New value available only at the end of the process cycle (after suspension)

Updated immediately (new value ready to be used in the next line of code)

6

Inference of registers

Flip-flops are inferred when an assignment to a signal occurs at the transition of another signal

Flip-flops are inferred when an assignment to a variable occurs at the transition of a signal and this variable affects some other signal

(*) Shared variables not considered (should not be used for synthesis).

Rule 1: Places of declaration

Signal: Can be declared in the declarative part of entity, architecture, package, block, and generate. Additionally, remember that all circuit ports are signals.

Variable: Can only be declared in sequential units (i.e., process and subprograms). The only exception is for shared variables, which can be declared in the same places as signals (but they are neither intended nor recommended for synthesis).

Rule 2: Scope (places of use)

Signal: Global. It can be read and modified anywhere in the architecture statement part, including inside sequential units.

Variable: Always local. It can be read and modified only inside the sequential unit where it was created (declared). To leave that unit, its value must be passed directly or indirectly to a signal. The only exception is for shared variables, which can be global (but should not be used for synthesis).

Rule 3: Assignment symbol

Signal: Values are assigned using “<=”. Example: sig <= a + b;

Variable: Values are assigned using “:=”. Example: var := a + b;

Rule 4: Multiple assignments

Signal: Contrary to concurrent code, in which a signal cannot receive more than one assignment, in sequential code multiple assignments are fine (the last value will prevail). Be aware, however, of combinational loops (section 12.12).

Variable: Multiple assignments are always fine (again, the last value will prevail).

Rule 5: Update

Signal: A new value is only available after the conclusion of the process cycle (after it suspends).

Variable: Updated immediately so its new value is ready to be used in the next line of code.

Rule 6: Inference of registers

Signal: Flip-flops are inferred when an assignment to a signal occurs at the transition of another signal.

Variable: Flip-flops are inferred when an assignment to a variable occurs at the transition of a signal and this variable affects directly or indirectly some other signal.

Example 12.4. Counters with signal and variable

Say that we need to implement two counters, both counting from 0 to 9, one being implemented with a signal and the other with a variable (code below). Our job is to determine whether the correct circuit is produced. (For simplicity, the types bit and integer are employed in this example, so the packages list is not needed.)

The entity declaration (lines 2–6) contains only the port field, where the only input is clk and the outputs are count1 (produced by the process with signal) and count2 (produced by the process with variable). The purpose of the range in line 5 is just for the compiler to determine the number of bits that it should assign to each output; so 0-to-10 and 0-to-15, for example, are equivalent (four bits in either case).

The architecture body (lines 8–33) contains two processes. The first, called counter1 (lines 11–19), implements a counter using a signal (note that the count1 port is used directly in the process). The second process, called counter2 (lines 21–31), implements a counter using a variable (called var, declared in line 22). Note that the value of var is passed to count2 (line 30) before the end of the process (recall that a variable can not leave its process).

Let us now check whether all six rules are observed and whether the right circuits result.

Rule 1 is not violated because the signal used in the first process is a circuit port (count1, line 5) and is hence a valid signal, and the variable used in the second process was created in the declarative part of the process (line 22), which is the correct location.

Rule 2 is fine too because the variable (var) was used only inside its own process. A signal can be modified anywhere in the architecture’s statement region, including inside a process.

Rule 3 was not violated because all assignments to count1 (lines 14 and 16), to var (lines 25 and 27), and to count2 (line 30) employed the proper assignment symbols.

Rule 4 too was not violated because multiple assignments (lines 14 and 16 for count1 and lines 25 and 27 for var) are allowed in sequential code.

Rule 5 is a little tricky. The assignment in line 14 increments count1, but because it is a signal, the new value will only be assigned at the conclusion of the present process cycle, so the test in line 15 is against the previous value of count1. Consequently, this counter will count up to ten, hence not fulfilling the range specification. Regarding var, its update is immediate, so the value assigned in line 25 is already available in line 26, thus producing the correct (0 to 9) range. Note that when var enters the process with value 9, it goes to 10 in line 25, but it is immediately zeroed in line 27, so when the process ends the value of var is indeed zero (the value var = 10 never reaches the output, so there is no “glitch” here).

Rule 6 tells us that when we want a result to be registered, the corresponding assignment must be made under the event attribute (directly or via a rising_edge or falling_edge function, as explained in section 12.2). In the code above, the assignments to count1 are under an if clk'event … statement (line 13), so count1 will be registered. The same is true for the assignments to var, which too are under this kind of statement (line 24).

Simulation results, confirming the incorrect range for count1 and correct range for count2, are shown in figure 12.7.

Figure 12.7
Simulation results from the code of example 12.4.

12.10    More about the Updating Rule of Signals and Variables

Because rules 5 and 6 are more difficult to master, further details about them are given in this section and the next.

Rule 5 says that variables are updated immediately, whereas signals are updated only at the end of the process (when it suspends). This is a common cause of mistakes for newcomers, particularly when a process not only must read a signal or variable but also must modify it (during a counter implementation, for example). Suggestions for both cases (with signal and with variable) are presented below.

When using signals    When using a signal, give preference to the read-then-modify approach, which prevents the risk of making a comparison against the signal’s previous value.

As an example, let us examine the two processes below, which implement a 0-to-9 counter. Process P1 first modifies the value of count (line 4), then reads (tests) it (line 5). It gives the impression that, when count = 9 occurs, count is zeroed, so value 9 never reaches the output, resulting a 0-to-8 counter. However, because the comparison is against the previous value of count, a 0-to-9 count indeed occurs, but the overall understanding is awkward. In process P2, count is read first (line 4), then modified (line 5), which makes everything perfectly clear (no abstraction needed).

When using variables    When using a variable, either approach is fine, but one must still remember that variables are updated immediately. Both processes below implement a 0-to-9 counter. In P3, the modify-then-read approach is used (lines 5–6), so the test (line 6) has to be against 10, which is converted to zero subsequently (line 7). In P4, the read-then-modify option is employed (lines 5–6), which has the advantage of being clearer because the value involved (9) is the actual end value, and we do not even need to know when i is updated. The former, on the other hand, can be slightly less verbose (illustrated in example 12.5).

Example 12.5. Timer with BCD outputs

In section 2.7.4 we saw two strategies for implementing the core of timers when binary-coded decimal (BCD) outputs are needed. In both the timer’s counter is broken into smaller serial counters so that BCD outputs are produced automatically (as shown in section 1.4.5, binary-to-BCD conversion would be expensive otherwise). This example shows two implementations using the approach of figure 2.20c for a timer that counts seconds continuously in the range 00 → 01 → … → 59 → 00 → … while enabled.

The entity declaration (lines 5–11) starts with a generic constant for the clock frequency (line 7), which is the only parameter that needs to be modified for the same functionality to result for any other clock speed. Note that the ports (lines 9–10) are all of standard-logic types.

The architecture’s declarative region (lines 14–22) starts with two constants (lines 15–16) that define the upper bounds for counters 1 and 2, which produce digits 1 and 2, to display units of seconds and tens of seconds, respectively. Next, a function that implements the display driver is entered, which will be called twice in the code proper (see the count1 and count2 conversions to digit1 and digit2 in lines 49–50). The complete function is presented in appendix E.

Finally, the architecture’s statement region (lines 24–52) is shown. It employs a single process (called P1, lines 25–51) to implement the target circuit. Three variables (lines 26–28) are employed to implement the three counters, which operate as depicted in figure 2.20c. The first variable (count0) produces an enable signal every second, which lasts a single clock period, during which count1 is incremented (so count1 represents units of seconds); when the enable pulse produced by count0 coincides with that produced by count1, count2 is incremented (so count2 represents tens of seconds). It is important to observe that P1 employs the read-then-modify approach (lines 32–33, 36–37, 40–41).

Another solution is presented in process P2, this time employing the modify-then-read option (lines 32–33, 35–36, 38–39). Note that the ranges of the counters are one unit larger (because of the modify-then-read approach). This might be confusing to some, but recall that these ranges are used only for the compiler to determine the number of bits needed to represent such variables. Process P1 has an advantage in that one does not need to remember that variables are updated immediately, while process P2 is slightly shorter. The resulting hardware is obviously exactly the same.

As a final comment, observe that signals digit1 and digit2 (lines 49–50 of P1 and lines 46–47 of P2) are not under the if rising_edge(clk) … statement (opened in line 30 of P1 and P2 and closed in line 48 of P1 and line 45 of P2); consequently, these signals are not registered (i.e., they are not stored in DFFs), which is the correct solution.

Example 12.6. Counter with is_max flag implemented with variable

Two solutions—both using a signal—for implementing a counter with a flag called is_max were seen in example 12.3. We show now four equivalent solutions, this time using a variable (recommended). In summary, processes P1 to P4 below produce the same circuit as processes P1 and P2 in example 12.3. (Recall, however, that the optimal solution is that treated in exercise 13.8.)

Process P1 is the simplest and most intuitive. It employs the modify-then-read approach; if, after incrementing, i has reached MAX (line 7), the flag is raised (line 8); otherwise, if i has passed MAX (line 9), the counter is returned to zero (line 10).

Process P2 is just another version of P1, illustrating the use of default values (lines 6–7).

Process P3 is a copy of P2 in example 12.3, with i declared now as a variable. Observe that now a modification in line 15 is needed to produce the same circuit. This is necessary because the increment in line 9 is already effective when the code reaches line 15, so MAX must be employed in place of MAX − 1.

Finally, process P4 is also a copy of P2 in example 12.3, but note that line 18 still uses MAX − 1 (exactly as in example 12.3). The reason for this is that two separate if rising_edge(clk) … statements are employed, so now the two subcircuits must be interpreted separately. More specifically, because the clock edge is applied at the same time to both subcircuits, the clock edge in the flag generator will capture the information made available at the counter’s output by the previous clock transition.

12.11    More about the Inference of Registers Rule

Rule 6 says that registers are inferred when an assignment to a signal (or variable) is made at the transition of another signal. Recall from section 12.2 that such transitions can be detected using the event attribute directly or, preferably, using the rising_edge and falling_edge functions (which employ the event attribute in their constructions).

As we saw in example 12.2, registers can also be inferred involuntarily (figure 12.2d) because of an incomplete sensitivity list. An incomplete sensitivity list can also lead to the wrong kind of register, as illustrated in the example below. The process on the left tries to implement a latch, which requires outp to be a copy of inp while clk is high, so it seems correctly constructed. However, the process only executes when the clock changes (because clk is the only signal in the sensitivity list), so the copy indeed occurs at the moment the clock changes from zero to one. Consequently, a positive-edge flip-flop, instead of a positive-level latch, is inferred. The problem is fixed in the process on the right by including inp in the sensitivity list.

It is also important to mention that, in the same way that it is necessary to place under if rising_edge() … (or an equivalent statement) any assignment that we want to be registered, it is important not to leave there what we do not want to be registered. This is illustrated in the next example.

Example 12.7. Flip-flop with q and qbar outputs

Say that we need DFFs with q and qbar outputs, but only DFFs with q are available (typical of FPGAs), so the qbar output must be routed as shown in figure 12.8a. Three candidate codes for that are presented below. Comments on them follow.

Figure 12.8
(a) Desired DFF with q and qbar outputs; (b)–(c) Are these equivalent implementations?

Process P1: There are two signal assignments (lines 17–18) under if rising_edge(clk) … (line 16), so both signals get registered (therefore, figures 12.8b and 12.8c are potential candidates). Because of qbar <= not d, the circuit of figure 12.8b results.

Process P2: For the same reason, both signals are registered. Because of qbar <= not q, the circuit of figure 12.8c is inferred.

Process P3: There is only one signal assignment (line 17) under if rising_edge(clk) … , so only that signal gets registered. Because qbar <= not q (line 20) is independent from the clock, this part of the circuit is combinational, so the (desired) circuit of figure 12.8a now results.

An interesting question here is whether the circuits of figures 12.8b and 12.8c would still produce the right functionality in spite of their extra register. The answer is that one of them will, whereas the other will fail completely. Can you elaborate on this?

Note: Modern compilers, in their simplification effort, are sometimes able to make little corrections (like the elimination of the second DFF above), but that is not guaranteed to happen or to produce the right fixing, so leaving it to the compiler is not a good design practice.

Example 12.8. Registered adder

This example too illustrates the inference of registers (rule 6). Say that we need to implement the registered adder of figure 12.9a, for which processes P1 to P3 below are offered. Our goal is to determine which one (if any) would produce the right circuit.

Figure 12.9
Registered adder implementations of example 12.8.

Process P1: Two signal assignments are made under if rising_edge(clk) … , so both will be registered. The first assignment is for a three-input adder, while the second re-registers the previous register, so the circuit of figure 12.9b results.

Process P2: Again, there are two signal assignments under if rising_edge(clk) … , so both will be registered. However, now the first assignment is for a partial sum, which is completed in the second assignment. The circuit of figure 12.9c now results.

Process P3: It contains a single registered signal assignment, with a three-input adder, hence inferring the right circuit (figure 12.9a).

Example 12.9. Shift register with odd implementations

The inference of registers is further illustrated in this example. Say that we need to implement the shift register of figure 12.10 (shift registers were reviewed in section 2.6.1), for which processes P1 to P4 below are offered. Our goal is to determine if any would produce the desired circuit.

Figure 12.10
Shift register of example 12.9.

Process P1: Only signals are used. The number of registers is equal to the number of signal assignments under if rising_edge(clk) … , so this process leads to the right circuit.

Process P2: Same as P1 but not recommended (it might be confusing).

Process P3: Variables are now employed. Because a variable is updated immediately, the four assignments collapse to just one (i.e., dout = din). Therefore, a single DFF will be inferred, which is not what we want.

Process P4: Because of the inverted order, the assignments cannot be collapsed, so the right circuit will result. But this also is not recommended (it might be confusing).
    The problem with the implementations that work in the example above is that they are not generic. The recommended approach for implementing shift registers is shown next.

Example 12.10. Recommended shift register implementation

The implementation in the next code is truly generic. The size of the shift register is defined by the generic constants NUM_BITS (line 7) and NUM_STAGES (line 8), so any shift register can be constructed by simply changing these two values. Note that only standard-logic types are used for the circuit ports (lines 10–12). Because the internal signal q (line 17) consists of an array of vectors, and such a type is not available among the predefined types, it had to be created (line 16). Observe that the shift register is obtained by a simple shifted concatenation (line 23) under an if rising_edge(clk) … statement (line 22).

Corresponding simulation results are presented in figure 12.11. As expected, at every positive clock transition the value of din advances one position, reaching dout after NUM_STAGES (= 4) clock cycles.

Figure 12.11
Simulation results from the code of example 12.10.

12.12    The Problem of Combinational Loops

As we know, sequential code can implement both sequential and combinational circuits. However, because of the absence of registers in the latter, a problem known as combinational loop can occur if the code is not written properly.

A combinational loop can occur when a signal is used recursively in a loop in an attempt to build a combinational circuit. Recall that a signal’s value is updated only when the process suspends, so in such loops the same value is fed back to the circuit, causing the inference of a direct (unregistered) feedback connection. Though the resulting solution is certainly not a useful circuit, it is synthesizable, which makes it even more important to be aware of such a problem. As a general rule, avoid the use of recursive signals in loops in sequential code; use variables instead. An actual case is presented next.

Example 12.11. Leading-ones counter with combinational loop

Say that we need to implement a circuit that informs the number of ones (called leading_ones), from left to right, before the first zero is found in a vector (called data) with BITS bits (this kind of circuit was treated briefly in section 12.7 and also in exercise 11.4). Assume that processes P1 and P2 below are offered, and our job is to determine whether they implement a correct solution.

Process P1: It is similar to that in one of the examples of section 12.7, so we know that it leads to a correct solution. Note that a variable (count_ones) is used in the loop. Simulation results, illustrating its correctness, are shown in figure 12.12a.

Figure 12.12
(a) Simulation results using P1 in example 12.11; (b)–(c) Registered versus unregistered loops; (d) Simulation results using P2.

Process P2: It was constructed with a signal (leading_ones) in the loop instead of a variable. Since a signal is updated only when the process suspends (which occurs only when the loop iterations are concluded), and there are no registers in this circuit (it is combinational), the result is an unregistered loop, so the output is fed back directly to the input as illustrated in figure 12.12c (figure 12.12b, which shows the registered loop of counters discussed in previous examples, was included only for contrast). Simulation results are shown in figure 12.12d, where the effect (ringing) of the combinational loop can be observed.

12.13    Additional Examples and Exercises

These are in chapter 13, which is dedicated entirely to practicing with sequential code. The list of all enumerated examples and exercises in this edition of the book is in appendix M.

13    Sequential Code: Practice

This chapter is a continuation of chapter 12. Both deal with sequential VHDL code, which allows the implementation of sequential and combinational circuits. While chapter 12 was dedicated to more conceptual aspects (nevertheless with practical examples included), the present chapter is dedicated entirely to practice with sequential code, including additional design examples and an extensive list of select exercises.

The list of all enumerated examples and exercises in this edition of the book is in appendix M.

13.1    Additional Design Examples Using Sequential Code

The following design examples using sequential code are presented in this chapter:

Example 13.1. Generic tree-type adder array

Example 13.2. Single-switch debouncer

Example 13.3. FIR filter with fixed coefficients

Example 13.4. Square root calculator

Example 13.1. Generic tree-type adder array

The first example in chapter 11 was for an absolute-difference calculator that required a tree-type adder array of nongeneric size for integers. At the end of that solution, it was mentioned that a generic tree-type structure would be simpler to implement with sequential code. That is the main purpose of this example, which shows a truly generic (any number of inputs and any number of bits per input) solution for an adder (or some other kind of processor) array organized in a tree-like fashion (figure 1.15b).

In what follows, it is assumed that the inputs are signed integers, so according to section 10.7, the right “arithmetic” type to represent them is signed. For simplicity, we assume that the circuit ports can use type signed (or any user-defined type based on it) directly. Recall that, as seen in section 1.5.4 (figure 1.15b), the number of extra bits needed at the output to prevent overflow is equal to the number of layers in the tree, i.e., EXTRA_BITS = LAYERS = ⎡log₂NUM_INPUTS⎤. Recall also that, according with table 9.4, the “+” operator for type signed requires the result to have the same size as the largest operand, so resizing is needed; consequently, a simple way of fulfilling the overflow requirement is to resize all data paths to NUM_BITS + EXTRA_BITS. Having established that, we can now develop a VHDL solution for this problem.

The approach chosen (among others) to be used in the code below is illustrated in figure 13.1a for six inputs. As shown in the gray area, dummy cells are added to the circuit until the number of inputs becomes a power of two (so a simpler code can be written), but value zero is applied to them, so the compiler will eliminate these cells in the simplification process.

Figure 13.1
(a) Approach used to implement the circuit of example 13.1; (b) RTL view produced by the compiler for the case of ten 7-bit inputs.

The first code below is for a package, called user_defined_type_pkg, that contains a user-defined type called signed_vector that is needed to represent the array inputs. This package is necessary because this kind of type is not available among the predefined types, and it is needed right at the beginning of the design code (for one of the circuit ports, line 12). Note that this package is listed in line 5 of the design code, where the declaration work.user_defined_type_pkg assumes that the package is present in the project directory (it can indeed be typed along with the design code, exactly as shown below).

The entity declaration, called adder_array_generic_tree (lines 7–14), starts with a generic list (lines 9–10) containing only NUM_INPUTS and NUM_BITS, which are the only two parameters that, in principle, should be in that list (but see pros and cons, plus other comments, in section 6.7). In the port list (lines 12–13), the number of extra bits needed for sum (line 13) is equal to the number of tree layers, which is calculated explicitly here; otherwise LAYERS (which is not an independent constant) would have to be in the generic list (that constant is declared eventually in line 17 of the architecture—again, see comments in section 6.7). Because of the ceil and log2 functions, the math_real package is required (line 4).

In the declarative part of the architecture (lines 17–19), two constants are created; while LAYERS gets the number of layers in the tree, PWR_OF_TWO converts the number of inputs to the next power-of-two value. Because in some places the name EXTRA_BITS is more telling than LAYERS, an alias is created for the latter.

In the statement part of the architecture (lines 21–41), a single process (lines 22–40) is employed to build the circuit. Note that a variable of type signed_vector (line 23) is employed to accumulate the sum. In the initialization portion of the process (lines 27–30), the actual inputs are resized to NUM_BITS + EXTRA_BITS (line 27–29) and passed to accum, while value zero is passed to accum in the dummy inputs (line 30). The second portion of the process (lines 33–38) implements the generic-size tree-type adding structure.

The RTL view produced by the compiler after processing this code for the case of ten inputs of 7 bits is shown in figure 13.1b. Note that it is exactly as expected, with a 4-bit extension in all branches.

Example 13.2. Single-switch debouncer

Debouncers were studied in detail in section 2.11.2, where two construction approaches were proposed, the first (figure 2.29a, with a simplified version in figure 2.29c) being for single-switch debouncers and the second (figure 2.30) for multi-switch debouncers. This example implements that of figure 2.29a.

The first thing to remember about deboucers is that the timer is relatively expensive in terms of hardware. The number of D-type flip-flops (DFFs), or bits, in the corresponding counter is ⎡log₂(Tdeb ● fclk)⎤ + 1 (the “+1” term is because of the strategy explained in section 2.11.2 regarding the most significant bit, or MSB); therefore, if Tdeb = 25ms and fclk = 50MHz, for example, 22 DFFs are needed to build the timer (the total number of DFFs for figure 2.29a would then be 24).

A VHDL code for the debouncer of figure 2.29a, with the optional input register included, is presented below. T_DEB_MS (debounce time in milliseconds, line 9) and F_CLK_KHZ (clock frequency in kHz, line 10) are generic parameters, based on which the constant COUNTER_BITS (line 17, explained in section 7.6.6), which determines the number of bits in the counter, is calculated. A single process (lines 21–42) is employed in the architecture, which is a straightforward translation of figure 2.29a. For more on this debouncer, plus those in figures 2.29c and 2.30, see exercises 13.12 to 13.16.

Example 13.3. FIR filter with fixed coefficients

Finite impulse response (FIR) filters were reviewed in section 2.11.6. This example shows a VHDL implementation for the FIR filter of figure 2.35a, with fixed coefficients b = {−8, −5, −5, −1, 1, 2, 2, 3, 5, 7, 7} (obviously, such perfectly ascending values are just illustrative). Observe that the structure of the adder-array part of this circuit is similar to that in figure 1.15a. The filter has eleven coefficients (so the filter length is 11 and the filter order is M = 10) and the number of bits in x[n] and bi is N = 4 (coefficients ranging from −8 to 7). Since this is an arithmetic circuit, the recommendations of section 10.7 will be followed.

A solution for this problem is shown below. The entity (lines 7–16), called fir_filter, starts with generic constants (lines 9–11), using names more meaningful than those in figure 2.35a. Note that this time a dependent constant, called EXTRA_BITS (line 11), was included in the generic list (see pros and cons and other comments in section 6.7). Next in the entity are the circuit ports (lines 13–15), which, as suggested in section 10.7, are all of standard-logic types.

The architecture’s declarative part (lines 19–30) starts with the declaration of a 1D×1D type (lines 21–22), with integer as the base type, employed to represent the filter coefficients (line 23; integers were used here to ease the declaration and readability of such coefficients). A second 1D×1D type was also declared (line 26), now using signed as the base type to represent all filter nodes (signals of lines 27–29) and to do the computations (hence in accordance with section 10.7).

The architecture’s statement region (lines 32–55) is divided in three parts. In the first (lines 34–41), a process is used to implement the shift register. Observe in figure 2.35a that that is indeed the only sequential circuit in the entire filter, so only for that part a process is required; the other two parts can be implemented using either sequential or concurrent code. Both the multipliers (lines 44–47) and the adders (lines 50–53) are implemented with concurrent code, using generate statements. The final value of the sum is converted from signed to std_logic_vector (line 54, using type cast) before being sent out.

Example 13.4. Sequential square root calculator

The square root of a number can be calculated using a combinational circuit (no clock needed—see exercise 11.18). However, if time is not a major constraint, a sequential circuit can do it with less hardware. An obvious solution is to build a counter and pass its output, n, through a multiplier to obtain n², and then to compare this result to the actual input, x; when n²>x occurs, we have reached the answer, which is y = n − 1 if rounding down is wanted (i.e., y = ⎣√x⎦).

We assume that the computation must start when a start pulse is received. To make the problem more interesting, we also assume the following: that start has an arbitrary duration (many clock cycles); that x might change after the computation has started; and, more importantly, that x and start are asynchronous with respect to our clock domain, so the important topic of synchronization of multi-bit data in clock-domain crossing (seen in section 2.9.1) can be practiced with. As will be seen, this problem poses an interesting opportunity for playing with RTL concepts.

We start by sketching an RTL diagram for this circuit (figure 13.2). The first step is to deal with the inputs (start and x), which must be synchronized, including a register for x (because x is not guaranteed to remain quiet during the computation). Two options for synchronizing multi-bit data were presented in section 2.9.1 (figures 2.22b and c), both showing that only the control signal (start, in this example) should be synchronized. This procedure is implemented in the SYNC sub-block of our circuit, employing the option of figure 2.22b. The two-flop synchronizer is associated to a one-shot circuit (figure 2.27a) to synchronize the pulse and, at the same time, to reduce its length to a single clock period. The mux is not shown because it is included in the enable port of the register (see figure 2.6a). The AND gate is needed to prevent a new pulse from being accepted while a computation is in progress. The resulting signal, start_sync, is the synchronous version of start. When this single clock period pulse occurs, it enables x to be registered locally (at the next positive clock transition), resulting in x_sync.

Figure 13.2
Sequential square root calculator of example 13.4.

The second sub-block (SQR ROOT) is the square root calculator. It employs a down counter (the explaination is ahead), which is set (taken to its initial state, which here is "111…1") when the start_sync pulse is high; at all other times, the counter is running (decrementing) if done = '0' or stopped otherwise. The reset signal is needed only to zero the counter at power up so the initial state of done is '1' (reset can be omitted if missing the first computation after power up is fine). The counter’s output (n) is turned into n² by the multiplier, being then compared to x_sync; when n² ≤ x_sync occurs, done goes to '1' (at the next negative clock edge), disabling the counter and, therefore, keeping y static from then on until a new valid start pulse occurs.

A downward counter was employed because the square of a k-bit integer does not cover the maximum value of a 2k-bit integer but the largest k-bit integer is the square root of the largest 2k-bit integer, so the down counter provides down rounding directly (i.e., y = n). In summary, an extra bit would be needed for n and additional circuitry would be needed for y (= n − 1) if an upward counter were employed.

Calling NUM_BITS the number of bits in x, the number of flip-flops needed to build this solution is as follows: NUM_BITS for the register for x, 3 for the sync plus one-shot circuit (figure 2.27a), ⎡NUM_BITS/2⎤ for the counter, and finally one for the done flag; for example, if x is a 16-bit value, 28 DFFs are needed.

The number of clock cycles needed to compute the square root can be as large as the range of n, which is 2 ⎡NUM_BITS/2⎤, so the time complexity is O(2NUM_BITS/2). For example, if NUM_BITS = 16, the range is 256, while for NUM_BITS = 32, it is ≈65k; therefore, with a 100MHz clock, it would take up to 2.6μs or 0.7ms, respectively, to calculate the square root.

A VHDL code for this circuit is shown next. It is divided in four modules (processes), which are direct translations of the subcircuits just described (figure 13.2). The reader is invited to analyze it and answer the following questions: Which line(s) of code in each process is (are) responsible for the inference of DFFs? How many DFFs in each case? The reader is also invited to replace the type unsigned used for ports x and y (lines 11–12) with type std_logic_vector. Finally, the reader is invited to compile and simulate this design.

The resources (hardware) usage, reported by the compiler after synthesis, can be given in lookup tables (LUTs), logic elements (LEs), adaptive logic modules (ALMs), and so on (this varies with FPGAs and vendors). The number of flip-flops is usually listed as FF or Number of Registers. For the present design, the number of LEs consumed, using a Cyclone IV FPGA, was 36 for NUM_BITS = 16 and 75 for NUM_BITS = 32. For comparison, a combinational implementation, using one of the nonrestoring square root algorithm versions (exercise 11.18) with the same FPGA, consumed 131 LEs for NUM_BITS = 16 and 608 LEs for NUM_BITS = 32 (as expected, the circuit growth with the number of bits is approximately linear in the sequential approach and quadratic in the combinational approach).

13.2    Exercises

Notes regarding the exercises below:

1)  Only the circuits in exercises 13.4 to 13.6 are purely combinational; all others are sequential, reviewed in chapter 2.

2)  In all exercises, use either sequential code or sequential code combined with concurrent code.

3)  For the circuit ports, use only standard-logic types (unless stated otherwise or a user-defined type is needed for the ports). If using generics, see section 6.7.

4)  For arithmetic circuits, observe the recommendations of section 10.7.

5)  For simulations, check appendix D. Only one graph is allowed (unless stated otherwise), so choose the stimuli carefully.

6)  The resources (hardware) usage is reported by the compiler after synthesis. It can be given in LUTs, LEs, ALMs, and so forth. The number of flip-flops is usually listed as FF or Number of Registers.

7)  Check the naming suggestions in section 5.8 (particularly, sections 5.8.1 to 5.8.4).

8)  For SSDs, check appendix E.

Part 1: Signal versus Variable

Exercise 13.1. Guess the circuit (1)

Below is a VHDL code with four (possibly equivalent) versions for the if statement of lines 18–28 in version 1 (note that it employs the read-then-modify approach for variable i). The range in line 15 must be adjusted in the other versions.

a)  Which versions employ the modify-then-read approach?

b)  Draw the waveforms produced by each version (show clk, i, and outp).

c)  Are the four outputs equivalent? What do these circuits do?

d)  Draw a possible RTL diagram for each case (hint: section 2.7.3 and example 12.6). What is the number of flip-flops? Which circuits are more economical to build?

e)  (Optional) Compile and simulate each version to check your conclusions.

Exercise 13.2. Guess the circuit (2)

The circuit in this exercise is a signal generator (section 2.6.6) that should produce the waveforms wave1 and wave2 of figure 13.3, each having i as the underlying counter. A corresponding code is shown below, with three options (P1 to P3) for the process, where one employs a variable to implement the counter, while the other two use a signal. Constants A, B, and C are generic parameters (lines 7–9).

Figure 13.3
Waveforms to be produced in exercise 13.2 and a possible implementation.

a)  Only one of these processes infers the correct circuit. Find it and explain why it works well. Compile then the code and show timing simulation results confirming that the results match figure 13.3 (observe note 5 above).

b)  Explain why each one of the other processes fails. Then compile each code and show simulation results confirming your conclusions.

c)  Show RTL details (for example, comparators and registers) for the waves generator block of figure 13.3. (Hint: See examples 12.3 and 12.6.)

Exercise 13.3. Guess the circuit (3)

The code with architecture arch1 below implements a famous mathematical series.

a)  Study it and draw an RTL diagram for the circuit that should be inferred by the compiler. What series does it produce?

b)  Which lines of arch1 cause the inference of registers? Why and how many DFFs in each case?

c)  Compile the code and compare the number of DFFs to your prediction above.

d)  Show the RTL view produced by the compiler. Does it resemble your RTL diagram?

e)  Show simulation results, confirming that the output is indeed the math series that you predicted. Observe note 5 above.

f)  Now, refer to arch2, in which variables are used instead of signals. Draw the circuit that you expect will be inferred by the compiler. Will it too produce that series?

g)  Note that there is no overflow check for the sum (line 26). Include that feature in arch1, adding to the circuit an output port for such a flag (call it oflow). Show then simulation results illustrating the operation of that flag.

Part 2: Combinational Circuits

Exercise 13.4. Hamming-weight calculator

a)  Why is the circuit in example 10.4 said to be combinational?

b)  Redo that design using sequential code.

c)  Which approach is simpler? Why?

d)  Show simulation results. Observe note 5 above.

Exercise 13.5. Largest cluster of ones

Solve exercise 11.5 (all parts) using sequential code.

Optional: If exercise 11.5 were solved already, compare the complexity of these two solutions.

Exercise 13.6. Generic chain- and tree-type maximum detector

A circuit receives (simultaneously) a collection of non-negative integers from which it must extract the largest value.

a)  Implement it using sequential code, adopting the chain-type architecture (figure 1.2a). The number of inputs (SIZE) and the number of bits per input (BITS) must be generic constants. Use the type unsigned for all circuit ports. (Hint: See example 13.3.)

b)  Show the RTL view produced by the compiler. Does it resemble the chain-type structure?

c)  Show simulation results for SIZE = 6 and BITS = 5. Observe note 5 above.

d)  Repeat part (a) using the tree-type approach (figure 1.2b). (Hint: See example 13.1.)

e)  Repeat part (b) for the solution in part (d).

f)  Repeat part (c) for the solution in part (d).

Part 3: Counters and Clock Dividers

Exercise 13.7. Counter with failure flag

Design a free-running counter from zero to MAX, including in it a failure flag (call it stopped) that is asserted as soon as the counter stops running and is lowered as soon as the counter restarts (“as soon as” means, at most, one-half of a clock period after the clock edge at which the counter did not commute or the counter resumed operation). Add also a counter-enable (ena) input, so the failure condition can be easily simulated. (Note: Check only whether the counter is running, not whether the sequence of states is correct; obviously, you must not use ena in the calculation of the flag.)

a)  Devise and explain, including a simple block diagram, a solution for this problem. Use as little hardware as possible (remember that comparators can be large circuits). (Hint: Do we need to monitor all counter bits to detect that it has stopped?)

b)  Implement your solution using VHDL. Enter MAX as a generic parameter and observe note 3 above.

c)  Show simulation results for MAX = 12 and MAX = 13 in two separate graphs, observing note 5 above.

Exercise 13.8. Counter with is_max flag

The 0-to-MAX counter of examples 12.3 and 12.6, with an is_max flag, was implemented with two comparators. Since comparators (section 1.5.8) can be large circuits, avoiding them is desirable.

a)  Can you sketch a solution (for example, some combination of figures 12.4a and 12.4b) using only one comparator?

b)  Implement it using VHDL.

c)  Show simulation results for MAX = 6 and compare the results to figure 12.5b.

Exercise 13.9. Direct and reversed gray counters

Gray counters were reviewed in section 2.6.5, with an implementation shown in figure 2.14b.

a)  Implement that (or an equivalent) Gray counter using VHDL, adding to it reset (rst) and enable (ena) ports. Enter the number of bits (NUM_BITS) as a generic parameter, and observe note 3 above.

b)  Show simulation results, for NUM_BITS = 4, observing note 5 above. Do the codewords match those obtained using the construction process described in section 2.6.5?

c)  Draw a circuit that does the opposite; that is, converts the Gray code back to conventional sequential code.

d)  Add this new circuit to the output of that implemented in part (a).

e)  Show simulation results demonstrating that correct back-conversion indeed occurs.

Exercise 13.10. Clock divider by any integer with symmetric phase

A circuit capable of dividing the frequency of the clock signal by any even or odd positive integer while still producing an output with symmetric phase (50 percent duty cycle) was described in section 2.7.2 (figure 2.18). In what follows, the clock divider (M) is called div.

a)  How many DFFs are needed to build it for div = 6, 7, 255, and 256?

b)  Implement it using VHDL, observing note 3 above.

c)  Check whether the number of DFFs inferred by the compiler matches each prediction of part (a).

d)  Show simulation results for div = 6 and div = 7 in the same plot. Adopt Tclk = 80ns and show about five or six periods of clk_out. Include plots for the following: clk, div, internal counter, and clk_out.

Exercise 13.11. Clock divider by odd integer with positive-edge-only flip-flops

A solution for this kind of problem was presented in figure 2.18, where one of the DFFs operates at the opposite clock transition compared to the others.

a)  Can you present a solution (block diagram or just the corresponding waveforms, with explanation) that divides the clock frequency by a positive odd integer (say, 5) and produces a symmetric phase (50 percent duty cycle) waveform under the restriction that all flip-flops must operate at the same (say, positive) clock edge?

b)  Is your solution guaranteed to be glitch free? Do not base your answer on simulation results—you might just have been lucky in the routing. (Glitches were discussed in section 2.4 and appendix D.)

c)  Assuming that you have found a solution, implement it using VHDL (regardless of being glitch free or not). Observe note 3 above.

d)  Show simulation results, observing note 5 above.

Part 4: Timers and Associated Circuits

Exercise 13.12. Single-switch debouncer with a test circuit

Debouncers were discussed in section 2.11.2, and a VHDL code for that proposed in figure 2.29a was shown in example 13.2. We turn now to the circuit of figure 13.4, which combines that debouncer with a test circuit. Assume Tdeb ≈ 25ms.

Figure 13.4
Debouncer plus test circuit of exercise 13.12.

a)  Review example 13.2, then synthesize that code with COUNTER_BITS = 3 (enter this value directly in line 17), which is the case in the time diagram of figure 2.29b. Next, show simulation results using the same stimuli of figure 2.29b (with Tclk = 40ns) and compare the results.

b)  In a few words, explain the operation of the test circuit of figure 13.4.

c)  Implement the complete circuit of figure 13.4 using VHDL (if you prefer, use the structural approach of exercise 13.40). Enter now the actual values of Tdeb and fclk and observe notes 3, 7, and 8 above.

d)  Demonstrate it in the FPGA board.

e)  (Optional) Can you present another (better, preferably) solution?

Exercise 13.13. Simplified single-switch debouncer

A simplified version of the debouncer of figure 2.29a was presented in figure 2.29c.

a)  Develop a time diagram for it (for N = 2) similar to that in figure 2.29b, using the same waveforms for clk and x.

b)  Implement that debouncer using VHDL. Adopt the same entity declaration as example 13.2.

c)  Simulate it (for N = 2) and compare the results to your plots in part (a).

d)  Add to your code the test circuit of exercise 13.12 and compile it for Tdeb ≈ 25ms and the actual value of fclk.

e)  Demonstrate it in the FPGA board.

f)  Say now that x comes from a pushbutton and y must only change (be inverted) when the pushbutton is pressed (but not when it is released). Explain how you will do it and modify your code for that to happen.

g)  Demonstrate this new solution in the FPGA board.

Exercise 13.14. Multi-input debouncer for non-concurrent switches

Say we need to debounce three switches, which are not supposed to be operated at the same time, so they can share a single timer.

a)  Based on the description given in approach #3 of section 2.11.2 and the hints in figure 13.5, draw a complete diagram for the case of three inputs (and a single counter).

Figure 13.5
Hints for a multi-input debouncer for non-concurrent switches (exercise 13.14).

b)  Implement it using VHDL, including the test circuit of exercise 13.12.

c)  Demonstrate it in the FPGA board using the same procedure as exercise 13.15.

d)  Say now that x comes from a pushbutton and y must be inverted when the pushbutton is pressed (but not when it is released). Explain how you will do it and modify your code for that to happen.

e)  Demonstrate this new solution in the FPGA board.

f)  (Optional) Can you present another (better, preferably) solution for part (a)?

Exercise 13.15. Multi-input debouncer for independent switches

Debouncers were discussed in section 2.11.2, with a multi-input solution for independent switches proposed in figure 2.30.

a)  Implement that debouncer using VHDL. Adopt the same generic parameters as example 3.2 plus NUM_SWITCHES (number of switches). Observe also note 3 above.

b)  Add to your design the test circuit of figure 13.4, connecting it to one of the switches and to its debounced version. If you prefer, use the structural approach of section 10.5.2.

c)  Demonstrate it in the FPGA board by playing with all switches simultaneously while checking the results (for one switch at a time) on the SSDs.

d)  (Optional) Can you present another (better, preferably) solution for part (a)?

Exercise 13.16. Multi-switch debouncer with clock gating

a)  In order to save power, make the modifications needed to introduce clock gating into the multi-switch debouncer of figure 2.30. Show circuit details clearly.

b)  Develop a time diagram for it that clearly exposes its operation (as in figure 2.29b, for example).

c)  Implement it using VHDL.

d)  Simulate it and compare the results to your plots in part (b).

e)  Now using Tdeb ≈ 25ms and the actual value of fclk, demonstrate its operation in the FPGA board using the same test circuit and procedure of exercise 13.15.

Exercise 13.17. Pulse-width modulator (PWM)

PWMs were reviewed in section 2.11.4. The purpose of this exercise is to implement a PWM with a time resolution (shortest pulse) of 60ns and a total of 8 distinct intensities (shades). In parts (a)–(d), assume fclk = 50MHz.

a)  Calculate Tclk_pwm (period of PWM clock), M (frequency divider), and BITS (number of bits in duty).

b)  Implement this circuit using VHDL. Enter fclk, Tclk_pwm, and BITS as generic parameters, and M as an internal constant. Following section 5.8.3, call them F_CLK, T_CLK_PWM, BITS_DUTY, and CLK_DIVIDER in the code. Call count1 and count2 the outputs of the counters used to produce clk_pwm and pwm, respectively (count2 is count in figure 2.32). For the circuit ports, observe note 3 above.

c)  Show simulation results in four separate graphs for duty = 0, 1, 6, and 7. Include in each the following plots: clk, count1, clk_pwm, count2, duty, and pwm.

d)  Are the waveforms for count2 and duty in the simulation similar to those in figure 2.32? More specifically, does the pwm pulse start when count2 = 0? If not, explain why (hint: see examples 12.3 and 12.6). (Optional: Modify your code until that is the case.)

e)  Devise a demonstration for your design in the FPGA board (one of the simplest demos is with three switches for duty and an LED for pwm). Remember to enter here the actual value of Tclk of your board.

Exercise 13.18. Timer (1)

The construction of timers with binary-coded decimal (BCD) outputs was seen in section 2.7.4 and example 12.5. The purpose of this exercise is to design the two-digit timer of figure 13.6a with the clear (synchronous reset) and run inputs produced by regular toggle-type switches and the main output exhibited on two SSDs (appendix E). The circuit must count seconds from 00 up to 60 while run = '1', stopping when 60 is reached or run = '0' occurs. When 60 is reached, the is_60 flag (connected to an LED) must be asserted immediately (i.e., at the same clock edge that takes the display to 60).

Figure 13.6
Timers of exercises 3.18 to 13.21.

a)  Are the overall circuits inside the two blocks of figure 13.6a combinational or sequential? Why?

b)  How many wires are there in cnt1, cnt2, dig1, and dig2?

c)  Implement this circuit using VHDL. Enter fclk as a generic parameter and observe notes 3, 7, and 8 above. Your first decision is which approach to use: figure 2.20a or 2.20c? (Which one was used in example 12.5?)

d)  Demonstrate it in the FPGA board. To speed up the demonstration, you can use a faster cnt1, like 2Hz instead of 1Hz.

Exercise 13.19. Timer (2)

This timer is an advanced version of that in exercise 13.18, again represented as in figure 13.6a but with the clear input removed and the run input produced by a pushbutton instead of a toggle-type switch. The circuit must count seconds from 00 up to 60, starting when the run pushbutton is pressed and stopping if it is pressed again or the final value (60) is reached. When 60 is reached, the is_60 flag (connected to an LED) must be asserted immediately (i.e., at the same clock edge that takes the display to 60). To clear the timer, run must be kept pressed for at least two seconds. (Recall that timers with BCD outputs were seen in section 2.7.4 and example 12.5.)

a)  Explain why a debouncer is needed for the run pushbutton.

b)  Implement this circuit using VHDL, without the debouncer for now. Enter fclk as a generic parameter and observe notes 3, 7, and 8 above.

c)  Test your solution in the FPGA board.

d)  Include now a debouncer for run (use that in example 13.2 or some other, with Tdeb ≈ 20ms). Instantiate it as a component using the approach of section 10.5.2.

e)  Demonstrate your final (complete) solution in the FPGA board.

Exercise 13.20. Kitchen timer (1)

The timer of figure 13.6b must count downward, starting from a value between 00 and 60 seconds set previously by the user. The initial value is set using the set_time pushbutton, which increments the time by one unit every time it is pressed; however, if set_time is kept pressed for two seconds or more, the time must start incrementing automatically at a rate of 2Hz, stopping if set_time is pressed again or 60 is reached. To start the timer (running downward), the start pushbutton must be pressed. The timer must stop if start is pressed again or the final value (zero) is reached. When zero is reached not due to reset, the is_zero flag (connected to an LED) must be asserted immediately (i.e., at the same clock edge that brings the display to 00). To reset the timer (also turning the LED off in case it is on), start must be kept pressed for at least two seconds (this should have precedence over any other command, at any time). A new cycle should only be allowed if the LED is off. (Suggestion: See section 2.7.4, example 12.5, and exercises 13.18 and 13.19.)

a)  Explain why debouncers are needed for both pushbuttons.

b)  Implement this circuit using VHDL, without debouncers for now. Enter fclk as a generic parameter and observe notes 3, 7, and 8 above.

c)  Test it in the FPGA board.

d)  Include now a debouncing circuit for set_time and start, with Tdeb ≈ 20ms. Since the pushbuttons do not need to act simultaneously, can they share some sort of single-switch debouncer? You can instantiate the debouncer using the approach of section 10.5.2.

e)  Demonstrate your final (complete) solution in the FPGA board.

Exercise 13.21. Kitchen timer (2)

Solve exercise 13.20 using, as a display, an alphanumeric LCD instead of SSDs. To do that, study first appendix H and the example in section 17.5 (digital watch with liquid crystal display).

Part 5: Synchronism

Exercise 13.22. One-shot and pulse-capture circuits

Two pulse-conditioning circuits were presented in section 2.11.1. The first (figure 2.27a) employs a synchronizer (when the input is asynchronous) plus a one-shot circuit, while the second (figure 2.27b) employs an edge detector plus a synchronizer.

a)  Study the one-shot circuit of figure 2.27a (including the synchronizer) and complete the waveforms in figure 13.7a. Is the limitation described in section 2.11.1 observed?

Figure 13.7
Waveforms of the pulse conditioners of exercise 13.22.

b)  Implement this circuit using VHDL. Observe note 3 above.

c)  Show simulation results, using the same stimuli of figure 13.7a (observe note 5 above). Do the results match your sketch?

d)  Repeat part (a) for the pulse capturer of figure 2.27b, completing the waveforms in figure 13.7b.

e)  Implement this circuit using VHDL. Observe note 3 above.

f)  Show simulation results, using the same stimuli of figure 13.7b. Do the results match your sketch?

Exercise 13.23. Pulse stretcher

Another pulse conditioner is presented here and consists of a circuit that is capable of stretching a pulse to a larger length. The application chosen to illustrate it is a memory interface circuit (figure 13.8a), which must generate two signals needed for writing data serially to a memory. As shown in figure 13.8b, it receives the signal named start, indicating that the data stream has just started, from which the signals we (write enable) and addr (address) must be produced (small delays were included in figure 13.8b to portray a more realistic situation). Assume that start is a clean (glitch-free) signal, but glitches in we following clock transitions are acceptable.

Figure 13.8
Pulse stretcher for the memory interface of exercise 13.23.

a)  Implement your solution using VHDL. Enter DEPTH (the memory depth, which is 6 in figure 13.8b) as a generic parameter, and observe note 3 above.

b)  Show simulation results, for DEPTH = 14. Observe note 5 above.

Exercise 13.24. Frequency meter

Frequency meters were discussed in section 2.9.2. The goal of this exercise is to implement the circuit of figure 2.24, including the pulse conditioner of figure 2.27a. The time window (write signal, figure 2.24c) has a duration of 1s, so the result, ranging from 0000 to 9999, is given in Hz automatically.

a)  Briefly describe the operation of this circuit. During what percentage of the time is this circuit actually measuring fx if fclk = 50MHz?

b)  Implement it using VHDL. The overflow flag (oflow), connected to an LED, must be asserted if fx ≥ 9999, with the display held at 9999 (saturation value). Enter fclk(CLK_FREQ) as a generic parameter, and observe notes 3, 7, and 8 above.

c)  Demonstrate your design in the FPGA board. To do that, add to your code a signal generator for x (so x becomes an internal signal) and test the circuit for the following values of fx, in Hz: 2, 3333, 9996, and 10100 (select the frequency with switches). Explain this new portion of your code. (If you prefer, you can instantiate the signal generator using the structural approach of exercise 13.41.)

d)  (Optional) Can you present a different solution for this problem?

Exercise 13.25. Measuring the width of asynchronous pulses

Figure 13.9 shows a randomly received pulse train p, which we must measure the width (w) based on our local clock (clk). Assume that fclk = 50MHz and 1μs ≤ w ≤ 1ms. The output must be given in nanoseconds. Because p and clk are mutually asynchronous, edges of p might occur within the forbidden region (tsetup + thold) of the flip-flops (as in the two gray shades), so this fact must be taken into consideration. (Hint: See section 2.9.)

Figure 13.9
Pulse-width measurement of exercise 13.25.

a)  Implement a solution for this problem using VHDL. Enter fclk as a generic parameter, and observe notes 3 and 7 above.

b)  Show corresponding simulation results. To simplify the analysis, use w < 8Tclk here. Observe also note 5 above.

Part 6: Shifters

Exercise 13.26. Tapped delay line

Tapped delay line was reviewed in section 2.6.9 with an M × N example shown in figure 2.16, where M is the number of blocks and N is the number of data bits.

a)  Implement that circuit using VHDL (if you prefer, use the structural approach of exercise 13.42). Enter M and N (call them BLOCKS and BITS, respectively, in the code) as generic parameters, and observe note 3 above.

b)  Show the RTL view produced by the compiler for M = 4 and N = 8.

c)  Show simulation results for M = 3, N = 3, and din = {0, 1, 2, 3, 4, 5, 6, 7, 0, 0, …}, in four separate graphs, for sel = 0, 1, 6, and 7, observing note 5 above.

Exercise 13.27. Pseudo-random sequence generator

Pseudo-random sequence generators, using a linear feedback shift register (LFSR), were reviewed in section 2.11.5. An additional example is shown in figure 13.10, which feeds two complementary LEDs (led₁ lights up when the output is high; led₂ lights up when it is low). The output must be updated every second, remaining on (actual pseudo-random signal) during 0.5s and off (both LEDs dark) during the next 0.5s.

Figure 13.10
LFSR of exercise 13.27.

a)  Is this a Fibonacci or Galois LFSR? What is its characteristic polynomial? After how many clock cycles will the output sequence start repeating itself?

b)  Implement it using VHDL. Enter fclk as a generic parameter and observe notes 3 and 7 above.

c)  Show the RTL view produced by the compiler.

d)  Demonstrate it in the FPGA board. Does the blinking of the LEDs look random?

Exercise 13.28. Continuously shifting display

Figure 13.11 refers to a circuit that must display the digits “0123” on four SSDs, entering continuously on the right and leaving on the left (see table) while ena = '1'. If clear = '1', the display must be (synchronously) blanked. The digit transfer rate, determined by a 4-bit thermometer code (see second table), must be 0.5, 1, 2, 4, or 8 transfers per second.

Figure 13.11
Continuously shifting display of exercise 13.28.

a)  This exercise can be solved in several ways with substantially different results in terms of hardware consumption. Propose a solution for it (including an RTL diagram) and briefly explain its operation. Observe that the ratios of the chosen speeds are all powers of two; can that fact help save hardware?

b)  How many DFFs are needed to build your solution, for fclk = 50MHz?

c)  Implement it using VHDL. Enter fclk as a generic parameter, and observe notes 3, 7, and 8 above. After compilation, write down the amount of hardware (see note 6 above) utilized by the compiler. Does the number of DFFs match your prediction?

d)  Demonstrate the circuit operation in the FPGA board.

Part 7: Controllers

Exercise 13.29. Reference-value generator

Reference-value generators were seen in section 2.11.3. The circuit in figure 13.12 must produce a reference value (ref) in the 0 to 20 range. Every time the up or dn pushbutton is pressed, ref must be incremented or decremented by one unit, respectively. The reset signal, produced by a toggle-type switch, must return ref to zero asynchronously. A major point here is that the output must be presented in two formats: regular binary value (ref) and BCD format (digits dig0 and dig1, displayed on two SSDs).

Figure 13.12
Reference-value generator of exercise 13.29.

a)  Explain why both pushbuttons must be debounced (assume Tdeb ≈ 20 ms). Explain also the debouncer you will use (see section 2.11.2 and exercises 13.12 to 13.16).

b)  There are basically three ways of producing the outputs: generate ref and then derive the BCD output from it; generate the BCD output and derive ref from it; or generate the outputs independently. Obviously, the hardware consumption varies from one option to the others. Explain how you will do it. (Hints: section 1.4.5, multiplier.)

c)  Implement your solution using VHDL. You can use one of the approaches in section 2.11.3 or some other. Observe notes 3, 7, and 8 above and instantiate the debouncer in your main code using the structural approach of section 10.5.2.

d)  Demonstrate your solution in the FPGA board.

Exercise 13.30. Air conditioning controller

Figure 13.13 refers to an air conditioning controller. Troom is the current room temperature, measured by a temperature sensor plus an 8-bit A/D converter (not shown), calibrated to produce "00…0" when Troom ≤ 60.0°F and "11…1" when Troom ≥ 85.5°F, hence with a resolution of 0.1°F (think of the following integers: 600, 601, 602, …, 855; the decimal point is added by the display). The desired room temperature (Tref) is set by the user by means of up and dn pushbuttons; every time up or dn is pressed, Tref must go up or down 1°F (so the corresponding integers are 600, 610, 620, …, 850). The default value (upon reset) must be Tref = 75°F. The controller must turn the heating system on (by means of heat = '1') when Troom ≤ Tref − ΔT (ΔT = 1°F is the hysteresis) or must turn the cooling system on (by means of cool = '1') when Troom ≥ Tref + ΔT, keeping it on in either case until Troom reaches Tref. The display must show Troom, except when display is pressed, in which case Tref must be displayed (so display must be kept pressed while Tref is being set by the user). Use pushbuttons for up, dn, and display; use switches or a slow counter or some other arrangement to produce Troom; use LEDs to display heat and cool; and use SSDs to display the temperature. (Suggestion: Review section 2.11.3 and first solve exercise 13.29.)

Figure 13.13
Air conditioning controller of exercise 13.30.

Note: If you prefer, use degrees Celsius (see range in figure 13.13).

a)  Present a block diagram for your solution and briefly explain it.

b)  Explain why two of the pushbuttons require (some kind of) debouncing and the other does not.

c)  Implement your solution using VHDL. Observe notes 3, 7, and 8 above. If you prefer, use the structural approach of exercise 13.43.

d)  Demonstrate it in the FPGA board.

Exercise 13.31. PWM breathing effect

The advanced PWM of figure 13.14, which consists of a PWM controller plus a regular PWM, must produce a ramp-like signal that stays low (0 percent duty cycle) during a time Tlow = 1s and then grows linearly during Tup = 2.6s, remaining high (100 percent duty cycle) during Thigh = 0.5s, and finally decreasing linearly during Tdown = 2.6s, repeating itself indefinitely. The number of light intensities should be 256, producing on the LED a breathing-like sequence of light intensities. A 2-bit thermometer-type input (speed) must also be included, which keeps the time parameters just given (for speed = "00") or decreases them by 25 percent (speed = "01") or 50 percent (speed = "11"). Assume that fclk = 50MHz. (Suggestion: Review section 2.11.4 and solve exercise 13.17 first.)

Figure 13.14
Breathing effect on an LED of exercise 13.31.

a)  If during Tup or Tdown each light intensity (0 to 255) is used exactly once, what will the LED’s blinking frequency (fpwm = 1/Tpwm) be? Is it large enough for the eye not to perceive the blinking? (Hint: What was the frame per second rate in old movies? And in the standard VGA video mode—see appendix I?)

b)  Calculate the clock division factor (M; call it CLK_DIVIDER if it is used in your code) and the number of bits (BITS_DUTY) in duty (see section 5.8.3 regarding constant names).

c)  Implement your solution using VHDL (if you prefer, use the structural approach of exercise 13.44). Enter only fclk (F_CLK) in the generic list (for the other parameters, use internal constants), and observe note 3 above.

d)  Demonstrate your design in the FPGA board (enter here the actual value of fclk).

Exercise 13.32. Quick-finger game

Figure 13.15a shows the top-level diagram of the quick-finger game’s circuit. The inputs are four pushbuttons (pb₀ to pb₃) to be activated by the player, a level-selection switch (level), and clock and reset. The outputs are a set of four LEDs (led₀ to led₃), an SSD to display the score (from 0 to F), and another LED (win) to be lit when the player beats the circuit. One LED, randomly selected, must blink at a time, after which the player must press the corresponding pushbutton. The player wins if they are capable of hitting the right pushbutton 15 consecutive times.

Figure 13.15
Quick-finger game of exercise 13.32.

Figure 13.15b depicts the clock waveform for the game (clk_game). It stays high during a fixed amount of time T₁ (during which one of the LEDs blinks) and low during a linearly decreasing time interval T₂ (during which the player must act). Adopt the following time parameters: T₁ = 0.25s (fixed); T₂ = 1.5s in the first interval, decreasing linearly to 0.6s (if level = '0') or to 0.3s (if level = '1') in the last time interval (which can be the 15th or 16th time slot, depending on how the first blink is treated).

a)  Explain why none of the switches requires debouncing.

b)  Implement this circuit using VHDL. Enter fclk as a generic constant and all other time parameters as internal constants. Observe notes 3, 7, and 8 above.

c)  Demonstrate it in the FPGA board.

Part 8: Serial Arithmetic Circuits

Exercise 13.33. Serial absolute difference between two vectors calculator

We saw in example 11.1 a parallel implementation for a circuit that calculates the accumulated absolute difference between two vectors a and b. We turn now to a serial counterpart, for which a partial RTL suggestion is given in figure 13.16. As in example 11.1, the length of a and b is M, and their elements are N-bit integers of type signed. A start pulse, with width Tclk, informs when a new serial stream starts. For simplicity, assume that the inputs are updated at negative clock edges, while our circuit is triggered at positive clock transitions. Finally, when the computation is concluded, a done flag must be asserted, remaining so until a new start pulse is received.

Figure 13.16
Serial absolute-difference calculator of exercise 13.33.

a)  What is the value of X (number of extra bits) as a function of M and/or N?

b)  Implement this or some another solution using VHDL. Enter both M and N (call them NUM_INPUTS and NUM_BITS, respectively, in the code) as generic parameters, observing notes 3 and 4 above.

c)  Show simulation results, with comments. Observe note 5 above.

Exercise 13.34. Moving-average calculator

Say that we need to design a circuit that calculates the arithmetic mean of the last S samples in a continuous serial data stream, where S is a power of two and each sample is an N-bit value of type unsigned. A possible solution is depicted in figure 13.17 and consists of an S-stage shift register (to store the last S samples), a pair of unsigned adders, and a sum register. To calculate the average we need to divide the sum by S; however, as S is a power of two, all that is needed is to right-shift the sum X positions, where X = log₂S, which is the same as taking only the sum’s N most significant bits (i.e., avg(N − 1:0) = sum_reg(X + N − 1:X)), though this provides always down rounding.

Figure 13.17
Moving-average calculator of exercise 13.34.

a)  What is the number of bits needed in the lines marked with question marks such that overflow never happens?

b)  How many flip-flops, as a function of S and N, are needed to build the solution of figure 13.17?

c)  Implement it using VHDL. Enter S and N as generic parameters with S = 8 and N = 4 as default values. Observe notes 3 and 4 above.

d)  Check whether the number of DFFs inferred by the compiler matches that from your equation.

e)  Show the RTL view produced by the compiler, and compare it to figure 13.17.

f)  Present simulation results, observing note 5 above.

g)  The solution in figure 13.17 tries to minimize the number of adders. A more obvious solution is to use a structure similar to figure 2.35a or 2.35b, without multipliers (bi = 1). Write down the number of flip-flops and of (two-input) adders that such a solution would require as a function of S and N, then compare them to the solution in figure 13.17 for S = 128 and N = 8.

Exercise 13.35. Determining the variance of video frames

The main purposes of this exercise are to deal again with RTL principles, to check the possibility of saving memory, and especially to play with arithmetic in VHDL (table 9.4 and section 10.7). For that, consider the video frame of figure 13.18a, where P = H × V is the number of pixels, for which we need to calculate the variance, var = Σ(si − m)²/(H∙V), where s represents the sample (pixel) values (N-bit non-negative integers) and m is their arithmetic mean over the entire frame. The pixel values are received serially, according to a local clock (clk), with a start pulse received at the beginning of each new frame, along with the first pixel value (for simplicity, assume that the inputs are updated at negative clock edges and our circuit operates at positive clock transitions). A done flag must be raised when the calculation is completed, with done and var remaining static until a new start pulse (or reset) is received. The circuit ports are shown in figure 13.18c.

Figure 13.18
Information relative to exercise 13.35.

a)  Some video computations require that the complete frame (or even more than one frame) be stored to do the calculations. Starting with the expression for var above, prove that that is not necessary here. Test your result using the numeric values given in the 5 × 3 frame of figure 13.18b.

b)  A preliminary RTL diagram for the circuit core (var calculation) is presented in figure 13.18d, where nearly all nodes are enumerated and the main signals are named. Notice the presence of two accumulators (acc1 for Σs² and acc2 for Σs), but no frame memory. Does this diagram match your equation from part (a)?

c)  A major improvement can (must) be made in the circuit of figure 13.18d. Do it.

d)  Present a sketch for the time behavior (as in figure 12.3, for example) planned for your circuit. Include plots for clk, start, s, var, and done (optionally, include also a plot for the internal counter that counts the number of pixels received). Use in the plot for s the initial five values of figure 13.18b.

e)  Solution 1: with type unsigned

e1)  List the minimum number of bits needed at each circuit node (2 to 12 in figure 13.18d) such that overflow never happens.

e2)  List now, for each node, the number of bits actually required by the involved operator (see table 9.4).

e3)  How many flip-flops are needed to build your solution? Remember that besides the two accumulators there are DFFs in the counter (0 to P − 1) and for the done flag.

e4)  Implement your solution using VHDL. Enter at least P (or H and V) and N as generic parameters. For the circuit ports, you are free to use standard-logic types or type unsigned.

e5)  Does the number of DFFs inferred by the compiler match your prediction?

e6)  Show simulation results, including plots for all signals with names in figure 13.18d, for N = 4 and P = 15, using maximum intensity for all pixel values (and 80ns time slots). What is the expected variance in this case? Compare it to the simulation result. Also, confirm that none of the circuit nodes has overloaded.

e7)  Repeat the simulation using now the values of figure 13.18b. Does var match that obtained in part (a)?

e8)  Finally, write down the amount of logic and DFFs (see note 6 above) used by the compiler for N = 4 and P = 15. (Compare it to that of your colleagues, if possible.)

f)  (Optional) Solution 2: with type ufixed. Repeat parts (e1) to (e8) using now type ufixed. Make assumptions and comments as needed (for example, is this advantageous only for large values of N? what happens if we restrict the output, or all nodes, to only N bits? etc.).

g)  (Optional) Solution 3: with type float. Repeat parts (e1) to (e8) using now type float. Observe the comments above. A major purpose here is to compare hardware consumption and precision among the three solutions.

Part 9: Filters

Exercise 13.36. FIR filter with underlimited data path

The purpose of this exercise is to design the pipelined FIR filter of figure 2.35c under the constraint that the internal communication after the multipliers must employ only 2N wires.

a)  How will you deal with the reduced number of bits? With truncation, saturation, resizing, or some other approach?

b)  Implement your solution using VHDL. Use the same approach and same values of example 13.3. Observe notes 3 and 4 above.

c)  Show simulation results, with explanations about the results. Observe note 5 above.

Exercise 13.37. FIR filter with programmable coefficients

The purpose of this exercise is to design the FIR filter with programmable coefficients of figure 2.36.

a)  Implement it using VHDL. Use the same filter coefficients in example 13.3, and observe notes 3 and 4 above.

b)  Show simulation results, with comments. Observe note 5 above.

Exercise 13.38. IIR filter with transposed direct form I architecture

Infinite impulse response (IIR) filters were reviewed in section 2.11.7. This exercise deals with that in figure 2.37b, known as transposed direct form I, where M₁ = 2 and M₂ = 3. Assume that N bits are used to represent the input and the filter coefficients.

a)  Write the minimum number of bits needed in each path of figure 2.37b such that overflow never occurs.

b)  Implement this filter using VHDL. Use fixed 4-bit coefficients (choose their values), stored in ROM-like memory (as in example 13.3). Observe notes 3 and 4 above.

c)  Show simulation results, with comments. Observe note 5 above.

Part 10: With Component Instantiation (Structural Code)

Exercise 13.39. Modular counter (structural code)

Figure 2.11a shows a traditional synchronous counter (typical of application specific integrated circuit [ASICs]). If we include an AND gate before the first stage, with its output connected to t₀ and both inputs connected to ena, a standard cell results (an AND gate followed by a T-type flip-flop, or TFF).

a)  Implement this circuit using VHDL. Design first the standard cell, then instantiate it in the main code using the approach of section 10.5.2. Enter the number of cells (NUM_BITS) as a generic parameter. Observe note 3 above.

b)  Show simulation results for NUM_BITS = 4. Observe note 5 above.

Exercise 13.40. Debouncer with a test circuit (structural code)

Redo the design of exercise 13.12, instantiating the debouncer as a component in the test-circuit code or vice versa. Choose the instantiation approach to be used (section 10.5.1 or 10.5.2).

Exercise 13.41. Frequency meter (structural code)

Redo the design of exercise 13.24 with the frequency meter in one design and the test signal generator in another. Put them together using the approach of section 10.5.2.

Exercise 13.42. Tapped delay line (structural code)

The design in exercise 13.26 is the sequential counterpart of that in example 11.2. Redo exercise 13.26 using the same approach of example 11.2 (i.e., with the delay line blocks instantiated as components). Use the instantiation option of section 10.5.2.

Exercise 13.43. Air conditioning controller (structural code)

Redo the design of exercise 13.30, instantiating at least the debouncer and reference-value generator (see exercise 13.29) as component(s). Choose the approach to be used (section 10.5.1 or 10.5.2).

Exercise 13.44. PWM breathing effect (structural code)

Redo the design of exercise 13.31, instantiating the PWM circuit of exercise 13.17 as a component. Use the approach of section 10.5.2.

14    Packages and Subprograms

There are two kinds of subprograms, which are called function and procedure. They are similar to process (studied in section 12.3) in the sense that these three are the only regions of VHDL code that are interpreted sequentially, so only sequential statements (see list under process in figure 12.1) are allowed. Subprogram calls (thus including all predefined operators) are allowed in both combinational and sequential codes.

A subprogram can be constructed in a package (including package body) or in the declarative part of an entity, architecture, process, or another subprogram. Because packages are the most common location (recall that all predefined operators are built in packages), we start this chapter with the study of packages and then proceed to functions and procedures.

14.1    Package

A package is a collection of declarations that are common to a model. Packages avoid common solutions from being rewritten, and this is indispensable for code organization, simplification, and reusability. The contents of most predefined (official) packages were studied at length in chapters 6–9. In chapters 10–13, we saw that user-defined packages are also allowed, and this is especially useful for creating data types that are not available among the predefined types and for constructing specific subprograms.

Because VHDL-2008 allows packages to have a generic list, the study of packages that follows is divided in two parts: without generics (in this section) and with generics (in the next section).

The general relationship between a package and a design entity was seen in figure 6.1. Note that a package consists of two parts, called package declaration and package body, constructed as shown below (for the case without generics).

The first part (package declaration) must contain only declarations, which can be declarations of subprogram, type, subtype, constant, signal, variable, file, package, component, alias, attribute, group, disconnection, property specification language (PSL), and use clauses. Note the possible presence of a libraries/packages list at the top of the package—a necessary item, for example, when a standard-logic type is employed in the declarations.

The second part (package body) is needed only when a subprogram declaration and/or a deferred constant (a constant with unspecified value, so the constant’s actual value is hidden from the model) declaration appear in the first part. (The package body might also be needed when a protected type is declared in the package, but that is usually for shared variables, which are not intended for synthesis.) The package body must then contain the subprogram’s body and/or the deferred constant’s value definition mechanism. The identifier (name) of the package body must be the same as that of the package.

The example below shows a package called example_pkg. This example contains only type, signal, and (a complete) constant declarations, so a package body is not needed. A case where a package body is required is presented in example 14.1.

Example 14.1. Package with function

The example below shows a package named subprograms_pkg. The package is declared in lines 2–5 and the body, in lines 7–19. The former contains two function declarations, so the latter must contain their bodies. The functions are called negative_edge and positive_edge, acting on type bit similarly to the falling_edge and rising_edge functions.

As we know, to use a package, a use clause must be included in the design unit. For the user-defined package above, the following line should be included before the entity declaration:

14.2    Package with Generics

As seen in section 6.7, VHDL-2008 allows the inclusion of a generic list in a package as well as the inclusion of a package declaration in a generic list. Moreover, the declarations allowed in the generic list, which before were only of constants, can also now be of types, subprograms, and other packages. (Be aware, however, that synthesis support for this might still be limited.)

The construction of a package with a generic list takes the form shown below; for synthesis, again, the package body is needed only if a subprogram declaration and/or a deferred constant declaration appear in the package.

The package with a generic clause above is said to be an uninstantiated package if it does not contain a generic map association (section 10.5); otherwise, it is said to be a generic-mapped package.

Before an uninstantiated package is used, it must be instantiated. This is done with the new keyword, as shown below, where the second option is for the case when all generics have default values.

An example is presented below. Note that package pack1 contains a generic clause, with a generic constant called BITS and a generic type called data_type (recall that, as seen in section 6.7, a generic type can not have a default value). This package also contains a regular declaration for a function called add, subsequently constructed in the package body. Because pack1 does not include a generic map association, it is an uninstantiated package, so an instantiation is needed before it is used. Such instantiation occurs in the second section of code below, in accordance with the syntax above and under the name pack2, where the generic map association defines BITS as 16 and data_type as bit_vector(7 downto 0). Finally, the third code refers to a design entity in which pack2 is made visible by the use clause.

As usual, a generic map association can be named or positional. In the example above, a named association was employed in pack2. Its positional counterpart is the following:

14.3    Function

As we know, VHDL code is inherently concurrent. The only exceptions are process and subprograms (i.e., function and procedure), which are regions of sequential code. However, contrary to a process, which is constructed in the code proper (i.e., in the statements part of the architecture body), a subprogram is constructed elsewhere, so the code remains simple and neat. Subprograms can be constructed in packages and in the declarative part of entity, architecture, process, or another subprogram. From a reusability perspective, construction in packages is obviously the indicated approach.

The main purpose of functions is to implement solutions for commonly encountered problems, like data type conversion, logical and arithmetic operations, and so on. A major example is the set of predefined VHDL operators (chapter 9), which are functions constructed in packages.

A function can be constructed as follows:

A function is pure when it does not rely on external object (signal or variable) values, so it returns always the same result when called with the same parameter values. In contrast, an impure function (not recommended for synthesis) is allowed to access external objects, so side effects can occur, and the result might be different even if called with the same parameter values. All functions that implement official predefined operators (chapter 9) are pure.

The function name can be any word that obeys the criteria for VHDL identifiers as described in section 5.7.6 (with suggestions in section 5.8.5). The input list can contain any number of parameters, which can be of classes constant (default), signal, and file (variable is not allowed). Regardless of the number of inputs, a function always returns a single object value, whose type must be that specified after the return keyword in the syntax above.

The above restriction that function inputs cannot be variables might be confusing. It means that the inputs cannot be declared locally as variables, not that they cannot be variables at the calling entity. This is illustrated in the examples below.

The declarative part of a function can contain the same kinds of declarations listed for process in section 12.3; again, signal declarations are not allowed, while variable declarations are the most common. Likewise, the statement part can contain only sequential statements, as in process, except for the wait statement.

Recall that subprogram calls (thus including predefined operators) are allowed in any kind of code (concurrent and sequential). By comparison, a procedure call is a stand-alone statement and can output any number of values (including zero), while a function call is always part of an expression and returns exactly one object value. The association list in a subprogram call can be named or positional, as illustrated below.

The use of internal variables is recommended in subprograms, particularly when loops are involved, in which case the inclusion of initial values in the variable declarations is just fine (review comments on signal versus variable in sections 6.6.2, 6.6.3, and 12.9). Notice that that is indeed the case in examples 14.2 to 14.5.

Four detailed examples of functions are shown next, with three of them also including a test circuit. As mentioned already, a subprogram can be constructed in a package (with package body) or in the declarative region of an entity, architecture, process, or of another subprogram. The examples that follow show function constructions in a package, in an architecture body, and in a process, which are the most commonly used options.

Example 14.2. Function ceil_log2 in a package

This example shows the construction of a function called ceil_log2, which calculates the log₂ of a positive integer rounded up—that is, ceil_log2(positive_integer) = ⎡log₂(positive_integer)⎤.

As seen in section 14.1, when a subprogram is located in a package, it must be declared in the package and constructed in the corresponding package body. The package, named subprograms_pkg, is presented in the first code below, with the function declared in line 3 and constructed in lines 7–14. It receives a positive integer named input (line 7) and returns its log₂ value, rounded up (line 13); for example, if the input is 20, it returns 5. A variable called result (line 8) and a loop statement (lines 10–12) are employed to do the computations.

A test circuit is presented in the second code, which contains a call to the ceil_log2 function in line 14. Note that the function call is part of an expression (not a statement), its return value being passed to the signal called outp. Note also the inclusion of a use clause in line 2 to make the package visible to the design unit. Simulation results from this code are presented in figure 14.1.

Figure 14.1
Simulation results from the code of example 14.2.

Example 14.3. Function ceil_log2 in an architecture

The code below shows the same function of the previous example, now constructed in the declarative region of an architecture (lines 11–13), which is another common place for constructing functions (though poorer from a reusability perspective). Note that the function is embedded directly in the test circuit, which produces the same results seen in figure 14.1.

Example 14.4. Function slv_to_integer in a process

This example shows the construction of a function named slv_to_integer, which converts the type of an object from std_logic_vector to integer. This time, the function (lines 18–28) is constructed in the declarative region of a process (hence again embedded in the test circuit, as in example 14.3). The inferred circuit is just a WIDTH-bit register, the function being used in the expression of line 31. (Though obvious, it is important to remember that this kind of type conversion does not consume any hardware; the reader can check that by compiling the code with lines 9 and 30 commented out and “(all)” added to line 17.)

Example 14.5. Function binary_to_bcd

This example shows a function called binary_to_bcd that converts a 12-bit unsigned binary value (thus ranging from 0 to 4095 in a decimal scale) into a 4-digit binary-coded decimal (BCD) representation (so with digits 4-0-9-5 when the input is "111…1"). It employs the double-dabble algorithm, exactly as seen in figure 1.10b. Both the input and output are of type std_logic_vector (line 2). Internally, type unsigned is used (line 3) because it has support for arithmetic operators. Corresponding simulation results are presented in figure 14.2. (For more on this, see exercise 14.1.)

Figure 14.2
Simulation results from the function of example 14.5.

14.4    Procedure

Compared to functions, procedures are used to implement multi-output problems. Moreover, procedures are stand-alone statements, while variables are used as part of expressions. Also, procedures can deal with time (using the wait statement, which is not allowed in functions) and can easily read from and write to files, which are important features for simulation (chapter 18).

On the other hand, the use and purpose of functions are often simpler, and most problems require just a single return value anyway (as in the predefined operators, studied in chapter 9), so the use of functions for synthesis is way more common than that of procedures.

The places where procedures can be constructed and called and the kinds of declarations and statements that they can contain are the same as those for functions (section 14.3), but wait is allowed. A procedure can be constructed as shown below.

The interface (in-out) list can contain objects of any class (constant, signal, variable, or file). Mode in includes constant (default), signal, and variable; modes out and inout include the same classes, but the default is variable; and files have no mode. These object class specifications refer just to how the inputs are treated locally, not to their classes in the calling entity; this is illustrated in the examples below.

Like function calls, procedure calls can be made in both concurrent and sequential codes. However, because the latter is a statement, the calls are substantially different. For comparison, examples for both are shown below.

Examples of function calls:

Examples of procedure calls:

Finally, the mapping association between a procedure call and the corresponding procedure declaration is similar to the mapping association for functions—that is, named or positional.

Example 14.6. Procedure find_min_and_max

This example shows the construction of a generic procedure that extracts the smallest and the largest values from a collection with any number of unsigned integers. The procedure is constructed in a package, shown in the first code below, and is tested using the circuit in the subsequent code.

The package (lines 5–9), named subprogram_pkg, contains only two declarations: a type called unsigned_array (line 6), needed to represent the input array, and our procedure, called find_min_and_max (lines 7–8). Note in the latter that the inputs are input_array (array of unsigned elements), ARRAY_SIZE (number of elements in that array), and ELEM_SIZE (number of bits used to represent each array element), while the outputs are min and max (smallest and largest array elements).

Observe also that the object class for the inputs was not specified, so they will be treated locally as constants (default), but they can be constants, signals, or variables in the calling entity. However, the outputs were specified as signals, so they must be treated as signals locally and in the calling entity (recall that signals cannot be created in the subprogram itself). If the outputs were left unspecified, they would have to be variables (default) locally and in the calling entity.

The package body (lines 11–35) contains the body of the find_min_and_max procedure (lines 13–33). Two variables, called minimum and maximum (line 15), are created to be used in the computations. The value of minimum is determined by a loop statement (lines 19–23) and is passed then to the min interface signal (line 24). The value of maximum is also determined by a loop statement (lines 27–31) and is passed then to the max interface signal (line 32).

In the test circuit (second code below), a simple procedure call statement (line 17) suffices to obtain the input array’s smallest and largest elements. Note that positional association was employed in this procedure call.

14.5    Function versus Procedure Summary

This section summarizes the main differences between function and procedure.

General purpose

Function: To implement solutions for commonly encountered problems, like type conversion, logical and arithmetic operations, and so on. Used more often than procedure for synthesis.

Procedure: To implement solutions for multi-output problems (values are modified rather than returned). It allows the use of time (with wait statement) and easy manipulation of files, which are important features for simulation.

Number of inputs and outputs

Function: Any number of inputs, with a single output.

Procedure: Any number of inputs and outputs, including bidirectional.

Interface list

Function: Inputs can be constant (default), signal, or file (variable is not allowed). Recall that signals cannot be created inside subprograms.

Procedure: Interface list can contain objects of any class (constant, signal, variable, and file). The default is constant for inputs and variable for outputs and bidirectional.

Nature of subprogram code and statements

Function: Sequential code, thus with only sequential statements (same as in process), with the exception of wait. Nested subprogram calls are allowed.

Procedure: Same as above, but wait is allowed.

Where subprograms can be constructed

Function: In a package (with package body) or in the declarative region of an entity, architecture, process, or another subprogram.

Procedure: Same.

Where subprogram calls can be made

Function: In both concurrent and sequential codes.

Procedure: Same.

Structure of subprogram calls

Function: A function call is part of an expression.

Procedure: A procedure call is a stand-alone statement.

Generic list with subprogram and subprogram with generic list (seen next)

Function: Allowed.

Procedure: Allowed.

14.6    Subprogram with Generics and Generic Subprograms

As seen in section 6.7, the inclusion of a generic list, before only allowed in entity declarations (and therefore indirectly also in components), was extended to packages and subprograms in VHDL-2008. Moreover, besides constants, now types, subprograms, and packages can also be included in a generic list. Both cases (subprogram with a generic list and generic list with a subprogram) are described in this section.

Subprogram with a generic list    The complete syntax for functions and procedures is shown below, now with a generic clause (subprogram header) included. The reserved word parameter is also included.

Similarly to packages, a subprogram without a generic list is said to be a simple subprogram (this is the case of nearly all subprograms). Otherwise, it is called an uninstantiated subprogram if it does not contain a generic map association or a generic-mapped subprogram if a generic map is present.

Before an uninstantiated subprogram is used, it must be instantiated, which is done with the new keyword, as shown below. In case all generics have a default value, the default keyword can replace the association list. The signature can be used to distinguish one version of the subprogram from another when multiple instances are available, similarly to what was done in the alias declarations for overloaded subprograms in sections 9.7.

Generic subprogram    A generic subprogram is a subprogram listed in a generic list. An example is presented next. First, it is assumed that several functions for adding two integers are available, two of which are shown below and called add_unsigned and add_signed, according to the type of the involved signals (these functions can be, for example, in a package—some details were omitted). Next, the code for a generic adder is presented, with a type (data_type) and a function (add) in the generic list, so this design entity (to be instantiated as a component in the next code) is indeed generic (unspecified). Finally, the main design entity, called signed_adder, is shown, in which the generic_adder circuit is instantiated as a component using the option seen in section 10.5.2. The corresponding generic map association list is responsible for specifying the generic parameters; the generic type (data_type) is defined to be signed(31 downto 0), and the generic add function is determined to be the add_signed function. In this example, both associations (generic map and port map) are named associations.

14.7    Overloaded Subprograms

Overloading was explained in section 9.2. Overloaded subprograms are subprograms that have multiple constructions under the same name. A classical example is the set of predefined operators (chapter 9), which are implemented by functions; for example, in table 9.4, the addition operator is available in many versions, all under the same name, “+” (indeed, overloading occurs in virtually all predefined operators, as can be seen in tables 9.2, 9.4, 9.5, 9.6, and so on). Any operator (predefined or user defined) can be overloaded; a detailed example on (further) overloading was included in section 9.2.

14.8    Assert and Report Statements

The assert and report statements do not infer hardware, which is why they were not studied earlier together with the classical (synthesizable) statements. Instead, assertions are used for checking the design, which is helpful especially for simulation (chapter 18). Assertions can be used in both sequential and concurrent codes.

The assert statement

A report is generated (displayed on the screen) by the statement above when the assertion is violated (i.e., when the assertion condition is false). Its content (message) can be static or dynamic. In the latter, the to_string function (described ahead) can be used together with the concatenation operator (&) to produce complex, useful messages that combine constant strings with measured/detected values.

The severity level can be note (to pass information from the compiler/simulator, without design problems), warning (to inform that something unusual was detected, which does not affect the model but can affect its behavior; for example, the inference of undesired latches when an incomplete truth table is entered in purely concurrent code), error (to inform that a serious unusual condition was found that does affect the model), or failure (for example, a bug in the design). The default threshold is error; in principle, it should not interrupt compilation or simulation, though the user might be allowed to select the halt condition. For synthesis, it is common to treat error and failure indistinctly, both stopping compilation.

The example below (for simulation) checks whether address is within the allowed range; if not, the message “Unexpected address value.” is issued and simulation is interrupted.

If the report clause is omitted, a default message (“Assertion violation,” for example) is exhibited on the screen, as illustrated below:

Below is an example of assertion in synthesis. Note in example 14.5 that there is no way for the compiler to know that an input with more than 12 bits should not be accepted. This can be fixed by including the following assertion between lines 4 and 5 (see exercise 14.1):

The report statement

In some cases, an unconditional report is wanted. That can be done by forcing the assertion condition to be false, as in the following example:

However, a simpler solution is to employ the report statement directly, using the syntax below that has note as default severity level:

The line in the example above can then be substituted with:

The to_string function

This function is often used as part of assertions for reporting dynamic values, like the time at which an error was detected during simulation. The main types allowed for the argument in to_string(arg) are time and all those listed in table 7.1, with the exception of boolean_vector, integer_vector, and real_vector. Strings can be concatenated using the concatenation operator (&).

The assertion in the example below is used in a simulation to check whether the result produced by the circuit matches the expected value. If it does not, the message shown in the first part of the report clause is issued, followed by the time value (now represents the current time value) at which the error occurred, converted to a string by the to_string function so as to be displayed on the screen. For example, if an error is detected at 25ns, the reported message is “Actual value does not match expected value at t = 25 ns.”

The next statement could be used to declare that the simulation ended and was successful, again with information about time attached to it:

Other examples using assertions and the to_string function will be seen in chapter 18.

14.9    Exercises

Exercise 14.1. Function binary_to_bcd

A function that converts a 12-bit vector (representing a 0-to-4095 integer) into four BCD digits was presented in example 14.5.

a)  Write a code for a circuit that tests that function, with a call like bcd < = binary_to_bcd(bin).

b)  Show simulation results, using the same stimuli of figure 14.2; then compare the results.

c)  Add to the function the assertion described in section 14.8. What happens when the code is recompiled?

d)  Recompile the code now using 13 bits for bin. What happens?

Exercise 14.2. Function integer_to_slv

Example 14.4 showed a function called slv_to_integer that converts the type of an object from std_logic_vector to integer.

a)  Write a function that now does the opposite; call it integer_to_slv and locate it in a package (as in example 14.2). Present also a test circuit for it (you can type the package and the test circuit together, saving that file with the entity’s name).

b)  Show corresponding simulation results.

Exercise 14.3. Function integer_to_onehot

a)  Write a function that converts an integer to a one-hot code. Call it integer_to_onehot and locate it in the declarative part of the architecture body of a corresponding test code (as in example 14.3).

b)  Show corresponding simulation results.

Exercise 14.4. Function leading_ones

a)  Solve exercise 11.4 using a function. Call it leading_ones and locate it in the declarative part of the architecture body of a corresponding test circuit (as in example 14.3).

b)  Show corresponding simulation results.

Exercise 14.5. Function Hamming_weight

a)  Solve exercise 13.4 using a function. Call it Hamming_weight and locate it in the declarative part of the architecture body of a corresponding test circuit (as in example 14.3).

b)  Show corresponding simulation results.

Exercise 14.6. Function sort_binary

a)  Write a function that sorts a binary vector (i.e., lists all '1's and then all '0's; for example, "00110111" would become "11111000"). Call it sort_binary and locate it in a package (as in example 14.2). Also present a test circuit for it (you can type the package and the test circuit together, saving that file with the entity’s name).

b)  Show corresponding simulation results.

Exercise 14.7. Function sla for std_ulogic_vector (overloaded operator)

a)  Write a function that implements the shift left arithmetic (sla) operator for type std_ulogic_vector (note in table 9.6 that it does not exist, so this operator will be further overloaded). Locate it in a package and present also a corresponding test circuit (as in example 14.2). You can type the package and the test circuit together, saving that file with the entity’s name.

b)  Show corresponding simulation results.

Exercise 14.8. Procedure find_min_and_max

a)  Compile the generic find_min_and_max procedure of example 14.6.

b)  Show corresponding simulation results.

Exercise 14.9. Procedure equalize_lengths

a)  Write a procedure that receives two vectors of type signed and applies signed extension to the shorter one (so only the shorter vector is modified). A call to this procedure could be, for example, equalize_lengths(in1, in2, out1, out2);. Locate the procedure in the declarative part of the architecture body of a corresponding test circuit (as in example 14.3).

b)  Show corresponding simulation results.

Exercise 14.10. Procedure add_signed

a)  Write a procedure that adds two signals of type signed, with carry-in and carry-out bits included. The addends and the sum must have the same number of bits. A call to this procedure could be, for example, add_signed(a, b, ci, sum, co);. Locate the procedure in a package (as in example 14.2). Present also a code for a corresponding test circuit.

b)  Show corresponding simulation results.

Exercise 14.11. Procedure mean_median

a)  Write a procedure that receives nine 8-bit signals (corresponding to the pixel intensities in a 3 × 3 block of a gray image) of type unsigned and returns their arithmetic mean and their median, also of type unsigned. A call to this procedure could be, for example, mean_median(pix1, pix2, …, pix9, mean, median); or mean_median(pixels_block, mean, median);. Locate the procedure in the declarative part of the architecture body of a corresponding test circuit (as in example 14.3).

b)  Show corresponding simulation results.

15    The Case of State Machines

As will be seen, there is nothing new regarding the VHDL language in this chapter (so this is indeed a case study). However, finite state machines (FSMs) are used very often and their design requires a great understanding of the VHDL language to develop the right implementation.

This chapter is dedicated to the more conceptual aspects of FSMs, leading to a rich collection of VHDL templates that cover all machine types (Moore, Mealy, and mixed) and all machine categories (regular, timed, and recursive). It is continued in chapter 16, which is dedicated entirely to practicing with such templates to design a number of circuits using the FSM approach.

State machines were reviewed in chapter 3, so the material in that chapter, plus reference [4], will serve as basis for this chapter and the next.

15.1    The Finite State Machine Approach

As seen in chapter 3, FSM is a very helpful modeling/design technique for sequential logic circuits. All sorts of controllers are potential candidates for it, especially when they involve a large number of if conditions. FSMs were reviewed in detail in chapter 3, so that chapter will serve as the basis for the material discussed here. We start by reviewing some very important conclusions and suggestions derived there.

1)  Never skip drawing the state transition diagram

This is a crucial step and the main cause of errors. To ease your task, use the Moore style whenever possible (conversion to Mealy can be done subsequently if necessary). To draw it, observe the three golden rules of section 3.2: a) the diagram must include all possible system states; b) all state transition conditions must be specified (unless a transition is unconditional), and they must be truly complementary; c) the list of outputs must be exactly the same in all states.

2)  Identify the machine category (regular, timed, or recursive)

Category 1 (regular FSMs) is the simplest; all transitions are time independent and all outputs are nonrecursive (do not depend on previous output values). In category 2 (timed FSMs), one or more transitions are time dependent, but the outputs are still nonrecursive. Finally, in category 3 (recursive FSMs), one or more outputs are recursive, without or with time-dependent transitions. Note that the machine category has nothing to do with the machine type.

3)  Define the machine type (Moore, Mealy, or mixed)

Because most digital systems are fully synchronous, give preference to Moore (default) whenever the problem specifications allow it. As a general rule, use Mealy only when the output must respond immediately to a change at the input or as a replacement for Moore with registered outputs when the latency introduced by the output register is unacceptable in the target application. The templates proposed in this chapter will allow this decision (between Moore, Mealy, and mixed) to be relatively simple.

4)  Simplifications in Moore-to-Mealy conversion are often unnecessary (or impossible)

Conversion from Moore to Mealy can be done as shown in section 3.4. The latter can have fewer (never more) states than the former, but because of the incremental way in which state transition diagrams are built (if constructed properly), it is highly unlikely that the reduction in the number states will be large and hence have low impact on the hardware (recall that the number of states must be reduced to less than 50 percent to guarantee the saving of just two flip-flops, if using minimal encoding). Consequently, first-order inspection (figure 3.4) is normally sufficient, and this will (in many or even most cases) indicate that skipping simplifications is just fine.

5)  You do not need to draw the Mealy diagram if simplifications are not made

If the Mealy version is going to have the same states as the original (Moore) version, there is no need to redraw the state diagram. The transition conditions and the output values can be easily read as Moore but typed in as Mealy, as will be shown in section 15.3.2 and also in the templates proposed ahead.

6)  Know well the VHDL templates for building finite state machines

Make sure to know well the VHDL templates for implementing FSMs described in this chapter, understanding the differences and pros and cons of each option. They cover any practical situation—some with higher modularity, others with lower verbosity—but there are other, equivalent ways to combine the modules in which the VHDL code is divided, so feel free to devise and use your own templates.

7)  Decide whether output registers are necessary

Recall that output registers (see figures 15.1, 15.2, and 15.3 ahead) are not part of the FSM itself. The main reasons for adding them are:

Figure 15.1
(a) Hardware architecture for regular FSMs; (b) Identification of registers (R1, R2) and combinational logic (L1, L2) blocks; (c)–(d) Examples used in the discussions.

Figure 15.2
(a) Hardware architecture for timed FSMs; (b) Identification of registers (R1, R2), combinational logic blocks (L1, L2), and timer (T); (c) Example used as reference for the templates.

Figure 15.3
(a) Hardware architecture for recursive FSMs (the timer is optional); (b) Identification of registers (R1, R2), combinational logic blocks (L1, L2), and timer (T); (c) Example used in the templates.

a)  To eliminate occasional glitches. Recall, however, that glitches can only happen in some cases (it depends on the combinational logic that produces the output) and only immediately after triggering clock edges in Moore machines, so they are not a problem in most applications.

b)  To produce a pipelined/RTL construction, as in figure 2.8c, and hence have a more predictable time behavior (less skew, possibly higher maximum operating frequency).

c)  To simply delay a signal or follow a synchronous design style.
However, if the FSM outputs go to another circuit that stores the received signals anyway, registering the outputs locally is obviously unnecessary.

15.2    State Encoding Styles

Another important aspect to recap before we start is the encoding schemes for the states of an FSM. The general principles were seen in sections 3.11 and 2.6.5. The particular cases of Vivado and Quartus Prime were seen in section 9.5.1 and are summarized below.

Vivado: Use fsm_encoding

Options: "sequential", "one_hot", "gray", "johnson", and "auto" (default).

Default encoding: Determined by heuristics (close to Quartus Prime’s default encoding).

Example:

Quartus Prime: Use syn_encoding

Options: "sequential", "one-hot", "gray", "johnson", "compact", "auto" (default), and user encoded.

Default: "sequential" for up to 4 states, "one-hot" between 5 and 49 states, and "gray" for 50 or more states.

Example:

15.3    VHDL for Regular (Category 1) State Machines

We start the study of VHDL for FSMs by discussing the implementation of regular state machines. First, their hardware structure (studied in chapter 3) is reviewed. Next, several VHDL templates are proposed and described, which cover any practical situation.

15.3.1    Hardware Architecture of Regular State Machines

As seen in chapter 3, regular FSMs are those that to not require any register besides the state register to function. Their hardware architecture is illustrated in figure 15.1a (gray regions), having x as input and y plus z as outputs. The upper block (with two sub-blocks) contains only (and all) combinational logic, which produces the boolean equations for the next state and for the outputs. The lower block contains only (and all) flip-flops, forming the state register that is responsible for holding the machine’s state. Another representation is shown in figure 15.1b, using short names: R1 is the state register, L1 is the combinational logic for the next state, L2′ (Moore, dashed line open) or L2″ (Mealy, dashed line closed) is the combinational logic for the outputs, and R2 is an optional output register (applied only to output z in figures 15.1a and b; see reasons for adding such register in comment 7 of section 15.1). Because it is simpler and fully synchronous, Moore is our default approach.

15.3.2    Simple Moore-to-Mealy Conversion

If, for the reasons already mentioned, simplifications are not going to be made in a Moore-to-Mealy conversion, only the first step (from figure 3.4a to 3.4b) of the conversion procedure is necessary. Because that is trivial, it can be done directly in the VHDL code (no need to convert/redraw the state transition diagram).

As an example, consider the FSM of figure 15.1c (notice that the input and outputs are those of figures 15.1a and b), without R2 for now. The processes in the first column below (Moore style) implement L1 and L2 separately, highlighting the fact that L2 depends only on the present state (line 27), so it is not subject to if conditions. The opposite occurs in the Mealy case (second column); notice in the process for L2 that the outputs depend on both the present state (line 10) and the inputs (lines 12, 15, 24, …), therefore being subject to the same if conditions as the next state. Consequently, L1 and L2 can be put together, as shown in the third column.

Conclusion: Examining this last code, we conclude that to convert from Moore to Mealy (without simplifications), all that is needed is to list the output values for the next state instead of for the present state. For example, observe that the output values listed in lines 9–10 are for state B (line 8), which is the next state, not for state A (line 6), which is the present state.

As a final note, notice that states A and D could have been merged in this example (because their outward transitions are equal), but the impact on the hardware would not be relevant.

15.3.3    VHDL Templates for Regular State Machines

This section shows VHDL templates for implementing regular (category 1) state machines, without and with output registers. The complete list, with nine options, is shown in table 15.1. Comments on that table follow.

Table 15.1

List of VHDL templates for regular state machines

Machine type: This column includes all three construction options (Moore, Mealy, and mixed).

Registered outputs: This column informs that both Moore and Mealy can be used when none or all outputs must be passed through registers. However, if some of the outputs must be registered and others not, then a mixed machine is needed in order to preserve the relative phase between the registered and unregistered outputs (see section 3.5).

Latency relative to ref: This column lists the latency of each solution relative to our reference (Moore machine without output register). The inclusion of a register at the output of a Moore machine introduces inevitably a latency of one clock period (assuming that the FSM and the output register are triggered at the same clock edge). On the other hand, a pure Mealy machine can produce outputs one clock period earlier than a pure Moore machine, so its latency is negative. Consequently, the output of a Mealy machine, when registered, has zero delay with respect to our reference, so Mealy can replace Moore when registering the outputs is necessary but extra latency is not acceptable.

Template to use: A total of nine templates are presented, covering any practical situation. Two equivalent options are offered for nearly all cases.

Modules: This column shows the number of modules (processes, in our case) used to construct each template. The distribution of such modules can be seen in the figures right below the table. Notice the use of L2′ in Moore, L2″ in Mealy, and both (L2′ plus L2″) in mixed machines. Higher modularity leads to clearer codes that are easier to understand and maintain, while lower modularity leads to slightly less verbose solutions.

Templates

The templates introduced next consist of a master template (M1), which covers the global portions of code, followed by specific templates (T1 to T9), which cover the portions of code that change from one case to another (they must be installed inside the master code). The machine of figure 15.1d (borrowed from figure 3.1), which models a nonoverlapping “010” detector (output y), is used as a reference. A dummy output (z) was added to the state diagram to better illustrate the templates. This simple machine was chosen because then we know well what the output values should be, which is most helpful in simulations (see exercise 15.1).

Notes:

1)  In what follows, it was assumed that the initial values of registered outputs (when they exist) are not important. To do otherwise, include reset values for them (for example, reset values for y and z before line 39 in T3).

2)  The proper template for a regular machine can be selected using table 15.1, but see also the simplifications of section 15.6 (figure 15.4).

3)  The templates can be shortened by using default values, which can always be listed at the beginning of any process. Values were shown explicitly here for the sake of clarity.

Figure 15.4
Template selection summary.

Master template M1: For regular (category 1) state machines

Master template M1 shows the general structure of regular FSMs, using figure 15.1d as example. The entity declaration (lines 4–10) is just like any entity declaration, possibly with generics (not shown). The architecture body (lines 12–27), on the other hand, is very different from any other seen before. In its declarative part (lines 13–22), an enumeration type called state_type (line 15) that contains all machine states is created; next, the machine’s pr_state and nx_state signals are declared as being of that type (line 16). In some of the templates, pr_state is called simply state and nx_state is not needed, so line 17 is used instead of line 16. Next, a state encoding attribute, like fsm_encoding or syn_encoding (section 15.2), can be optionally included (lines 20–21); otherwise the encoding style must be set in the synthesis tool (this is prone to error if a specific style is wanted; the encoding attribute has the advantage of overriding the tool setup). The next part, which is the statement part of the architecture body (lines 24–26), is covered by the chosen template (from T1 to T9).

Template T1: For regular Moore machines, with none of the outputs registered (3 modules)

This is a pure Moore implementation (no output registers). Therefore, it contains only the three blocks in gray (R1, L1, L2′) of figures 15.1a and b. Template T1 contains three processes, implementing all blocks separately. The advantage of this full modularity is that the code is simpler to understand and debug.

The first process (lines 4–11) implements the state register (block R1 of figure 15.1b), which stores the value of nx_state at every (positive) clock transition. Notice that, except for the name of the reset state (which sometimes is not needed), the code for this block is always the same, which is most helpful.

The second process (lines 14–34) implements the logic for the next state (block L1 of figure 15.1b). Because this block always receives both the present state and the actual FSM input, its style is independent from the machine type (Moore or Mealy) and it is expected to have if statements.

Finally, the third process (lines 37–53) implements the logic for the outputs (block L2 of figure 15.1b, called L2′ when it is Moore or L2″ when it is Mealy). This is the only place where Moore and Mealy are different; because L2 in a Moore machine does not depend on the input (the dashed line in figures 15.1a and b is open), the output is unique in each state, so if statements cannot show up here.

An obvious (and fine) variation for T1 is to put all combinational logic (blocks L1 plus L2′) together in a single process (i.e., to merge the processes of lines 14–34 and 37–53).

Template T2: For regular Moore machines, with none of the outputs registered (2 modules)

T2 is equivalent to T1. The only difference is that blocks L1 and R1 of figure 15.1b are now built together. That is fine because that pair of blocks is independent from everything else (it does not depend on whether it is a Moore or Mealy machine or whether the outputs are registered or not). A slightly less verbose code results (notice, for example, that pr_state and nx_state are replaced simply with state).

Template T3: For regular Moore machines, with all outputs registered, so with latency (3 modules)

T3 is a copy of T1, with the only difference that an if rising_edge(clk) statement was added to it (line 39), causing the outputs to be registered (this obviously adds one clock cycle of latency). See comments about lines 40–41 in template T9.

Template T4: For regular Moore machines, with all outputs registered, so with latency (2 modules)

T4 is equivalent to T3. It is a copy of T2, with the only difference that the if rising_edge(clk) statement of line 37 was added to it, causing the outputs to be registered.

Template T5: For regular Moore + Mealy machines, with some outputs registered and no latency (4 modules)

To preserve the phase between the unregistered and registered outputs, the latter must be of Mealy type, so a mixed machine is needed here. The process in lines 38–50 is unregistered, thus implementing a Moore-type output (notice the use of pr_state in line 40). On the other hand, the process in lines 54–68 is registered (see the if rising_edge(clk) statement in line 56), but the output is of Mealy type (see that nx_state is used in line 57).

Template T6: For regular Moore + Mealy machines, with some outputs registered and no latency (2 modules)

T6 is equivalent to T5, just with a different (more compact) combination of blocks (see T6 in the lower part of table 15.1).

Template T7: For regular Mealy machines, with none of the outputs registered, so with negative latency (2 modules)

This is a pure Mealy implementation (no output registers). Because in Mealy machines blocks L1 and L2″ are very similar (both involve if statements, and such statements are virtually the same), writing them together prevents duplication.

It is very important to observe that the listed output values are for the next state, not for the present state (recall the conclusion of section 15.3.2); for example, the values listed in lines 20–21 are for state one (line 19), which is the next state, not for state zero (line 17), which is the present state.

Template T8: For regular Mealy machines, with all outputs registered and no latency (1 module)

This is similar to T7, but now the outputs are registered (notice that they are under the elsif rising_edge(clk) statement of line 8). Observe again that the listed output values are for the next state, not for the present state (recall the conclusion of section 15.3.2); for example, the values listed in lines 13–14 are for state one (line 12), which is the next state, not for state zero (line 10), which is the present state. This is obviously the shortest code one can write. Its downside is the lack of structural details, but most registered Mealy machines are simple enough for this to be just fine.

Template T9: For regular Mealy machines, with all outputs registered and no latency (3 modules)

T9 is equivalent to T8, but more modular. It is indeed a copy of T3, just with line 41 in place of line 40. Because here we look at nx_state instead of pr_state, both outputs are of Mealy type (recall the conclusion of section 15.3.2). Therefore, they are both registered but in phase and without latency with respect to our reference.

Templates for regular Moore machines with none of the outputs registered (thus no latency⁽*⁾ is added)

Templates for regular Moore machines with all outputs registered (thus with latency⁽*⁾ added)

Templates for regular mixed Moore-Mealy machines with some outputs registered and no latency⁽*⁾

Template for regular Mealy machines with none of the outputs registered (thus with negative latency⁽*⁾) and template for regular Mealy machines with all outputs registered (zero latency⁽*⁾)

Template for regular “apparently Moore” Mealy FSMs with all outputs registered and no latency⁽*⁾

15.4    VHDL for Timed (Category 2) State Machines

This section is dedicated to the study of VHDL for timed state machines. Their hardware architecture (studied in chapter 3) is reviewed, and then a set of VHDL templates, covering virtually any practical situation, are proposed.

15.4.1    Hardware Architecture of Timed State Machines

As seen in chapter 3, timed FSMs are those that have one of more time-dependent transitions (still without recursive outputs). Their hardware architecture is depicted in figure 15.2a. The only difference with respect to figure 15.1a is the presence of a timer, whose output (t) acts as an auxiliary input to the FSM; however, it is the FSM itself who must control the timer, deciding when it should run or stop and when it should be zeroed.

A representation using short names for all blocks is shown in figure 15.2b; it is similar to figure 15.1b except for the presence of a timer (T). These blocks also indicate in how many modules a template can essentially be divided.

An example is shown in figure 15.2c (borrowed from figure 3.16). It models a debouncer for a mechanical switch (a keyboard key, for example), x being the (glitchy) input and y the (clean version of x) output. Only when x stays high or low during Tdeb consecutive clock cycles is its value passed to y. A dummy signal (z) was added to the state diagram to better illustrate the templates. Recall that the “−1” factor in Tdeb − 1 is because we assume the counter that implements the timer starts from zero, so to span Tdeb clock periods, it must count from 0 to Tdeb − 1. (Obviously, this “−1” does not matter in this particular application, but it is a reminder of how the timer is usually operated.)

Recall the general criteria for controlling the timer, seen in section 3.14, which says the following:

1) For stopping the timer: Stop the timer when it reaches the monitored value (or a predefined value above that), keeping it so until the machine changes its state. Keep the timer stopped (at zero) in the states where it is not needed.

2)  For zeroing the timer: Zero the timer when the machine changes its state.

These criteria are used in all templates that follow.

15.4.2    VHDL Templates for Timed State Machines

This section introduces VHDL templates for implementing timed (category 2) state machines, without and with output registers. The complete list, with five options, is shown in table 15.2. Comments on that table follow.

Table 15.2

List of VHDL templates for timed state machines

Machine type:    This column includes again all three construction options (Moore, Mealy, and mixed).

Registered outputs:    This column informs that both Moore and Mealy can be used when none or all of the outputs must be passed through registers. However, if some of the outputs must be registered and others not, then a mixed machine is needed to preserve the relative phase between the registered and unregistered outputs (section 3.5).

Latency relative to ref:    This column lists the latency of each solution with respect to our reference (Moore machine without output register). The interpretation and conclusions are the same as for table 15.1.

Template to use:    A total of five templates are presented, covering any practical need.

Modules:    The number of modules (processes, in our case) is listed in the last column, and the corresponding distributions of blocks are depicted in the figures right below the table. Recall that block L2 is called L2′ for Moore and L2″ for Mealy (so mixed machines have both). Higher modularity leads to clearer codes, easier to understand and maintain, while lower modularity leads to slightly less verbose solutions.

Templates

Comments on master template M2 and associated templates T10 to T14 are presented next. The machine of figure 15.2c is used as reference.

Notes:

1)  In what follows, it was assumed that the initial values of registered outputs (when they exist) are not important. To do otherwise, include reset values for them (for example, the reset value for y before line 39 in T12).

2)  The proper template for a timed machine can be selected using table 15.2, but see the simplifications of section 15.6 (figure 15.4).

3)  The templates can be shortened by using default values; these can always be listed at the beginning of any process. Values were explicitly shown here for the sake of clarity.

Master template M2: For timed (category 2) state machines

It shows the general structure of timed FSMs, using figure 15.2c as example. The entity declaration (lines 4–13) is like any entity declaration, with generics included in this example. The declarative part of the architecture body (lines 16–28) is similar to that seen for regular machines, except for the timer-related declarations (lines 26–27). The statement part of the architecture body (lines 30–50) consists of processes that are covered by the templates (shown next) plus a process for the timer (lines 32–47).

Notice that the code for the timer is a standard code (the same for any problem), which is most helpful. First, the value of tmax (value up to which the counter that implements the timer must count) is defined for each state (lines 34–39), with tmax = 0 in the states where the timer is not needed. Next, notice the test in line 41; if the next state is different from the present state, it means that the machine will change its state at the next clock transition, so the timer is zeroed (line 42), complying with the time-zeroing criterion mentioned earlier. Notice also the test in line 43; when the timer reaches the monitored value (tmax), it stops, remaining so until the machine changes its state, thus complying also with the criterion for stopping the timer mentioned earlier.

Template T10: For timed Moore machines, with none of the outputs registered (4 modules)

T10 is similar to T1, with the only difference that the timer (shown in master template M2) was added to it. Note that time indeed appears now in the if clauses (line 29, for example). Similarly to T1, an obvious (and fine) variation for T10 is to put all combinational logic (blocks L1 and L2′) together in a single process (i.e., to merge the processes of lines 17–39 and 42–58).

Template T11: For timed Moore machines, with all outputs registered, so with latency (4 modules)

The only difference with respect to T10 is the presence of the if rising_edge(clk) statement in line 44, which causes the outputs to be registered.

Template T12: For timed Moore + Mealy machines, with some outputs registered and no latency (5 modules)

The outputs are separated in two processes. The first (lines 37–47) implements the registered output (signal y; see the if rising_edge(clk) statement in line 39), but Mealy style is used (notice nx_state in line 40), so it is without latency. The second process (lines 51–59) implements the unregistered output, so Moore style is now employed (notice pr_state in line 53).

Template T13: For timed Mealy machines, with none of the outputs registered, so with negative latency (3 modules)

This is a compact purely Mealy implementation. It is very important to observe that the listed output values are for the next state, not for the present state (recall the conclusion of section 15.3.2); for example, the values listed in lines 23–24 are for state still_zero (line 22), which is the next state, not for state zero (line 20), which is the present state.

Template T14: For timed Mealy machines, with all outputs registered and no latency (4 modules)

T14 is a copy of T11, just with line 46 used instead of line 45. Because now we look at the next state, a Mealy-type construction results, hence with zero latency with respect to our reference.

Template for timed Moore machines with none of the outputs registered (thus no latency added) and template for timed Moore machines with all outputs registered (thus with latency⁽*⁾ added)

Template for timed mixed Moore-Mealy machines with some outputs registered and no latency⁽*⁾ and template for timed Mealy machines with none of the outputs registered (thus with negative latency⁽*⁾)

Template for timed “apparently Moore” Mealy FSMs with all outputs registered and no latency⁽*⁾

15.5    VHDL for Recursive (Category 3) State Machines

This section is dedicated to the study of VHDL for recursive state machines. Their hardware architecture (studied in chapter 3) is reviewed, and then a set of VHDL templates, covering virtually any practical situation, are proposed.

15.5.1    Hardware Architecture of Recursive State Machines

As seen in chapter 3, recursive FSMs are those that have one of more outputs that depend on previous output values. Consequently, such outputs must be registered because the outputs of an FSM are produced by combinational logic, so when the machine changes its state it does not “remember” what the previous output values were.

The hardware architecture of recursive machines is depicted in figure 15.3a. The only difference with respect to figure 15.2a is that there is a recursive output here (just z, but it could be any number of outputs), so registering it is mandatory (notice that z is fed back to the combinational logic block that produces the outputs). The timer might or might not be needed.

Another representation, using the same short names as before for all blocks, is shown in figure 15.3b. These blocks indicate the nature of the circuits (logic or register) and also in how many modules a template can essentially be divided.

15.5.2    VHDL Templates for Recursive State Machines

This section introduces VHDL templates for implementing recursive (category 3) state machines, with registers for some or for all output signals. The complete list, with four options, is shown in table 15.3. The machine of figure 15.3c is used in the templates.

Table 15.3

List of VHDL templates for recursive state machines

Note: A major difficulty in recursive FSMs is the interpretation of the recursive assignments. Because such assignments are registered, the actual values will only be ready at the next clock cycle. Consequently, if we want the equations to be valid as they appear in a Moore diagram, the actual implementation has to be of Mealy type. For example, if i = i + 1 appears in state A of a Moore state diagram but the diagram is implemented as Mealy, then, yes, the result of this sum will be ready in state A itself. In summary:

1)  Use a Moore state diagram;

2)  If all outputs must be registered, implement the Moore diagram using only Mealy style (template T17 or T18);

3)  Otherwise, implement the Moore diagram using the Mealy style for registered outputs and the Moore style for unregistered ones (template T15 or T16).

4)  If time is involved, then the choice is limited to T15 and T17.

Machine type: This column of table 15.3 shows that only Mealy or mixed machines will be employed in the templates (for the reason explained in the note above).

Registered outputs: This column informs that at least some (the recursive) outputs must be registered.

Latency relative to ref: Because Mealy style is employed for registered outputs and Moore style is used for unregistered ones, latency with respect to our reference (Moore machine without output register) does not occur and the relative phase of all outputs is preserved.

Template to use: A total of four templates are presented, covering any practical case.

Modules: The number of modules (processes, in or case) is listed in the last column, and the corresponding constructions are shown in the figures right below the table. Recall that block L2 is called L2′ for Moore and L2″ for Mealy (so mixed machines have both L2′ and L2″).

Templates

Comments on master template M3 and associated templates T15 to T18 are presented next. The machine of figure 15.3c, borrowed from figure 3.20, is used as reference. It models a generic-size counter using only three states. A dummy signal (j) was added to it to better illustrate the templates. Even though such circuit is very simple, and so the FSM approach is not needed, it is perfect for our present analysis because we know what the output values should be, which is most helpful in simulations (see exercise 15.3).

Notes:

1)  In what follows, it was assumed that the initial values of registered outputs (when they exist) are not important. To do otherwise, include reset values for them (for example, the reset value for i before line 46 in T15).

2)  The proper template for a timed machine can be selected using table 15.3, but see also the simplifications of section 15.6 (figure 15.4).

3)  There are some assignments in the templates below that are redundant (like i = i), included just for clarity (the unaffected keyword or the null statement, seen in section 12.5, would have the same effect). They are redundant because when the outputs are registered, the list of outputs does not need to be the same in all states.

4)  Recall also that default values can always be listed at the beginning of any process.

Master template M3: For recursive (category 3) state machines

This shows the general structure of recursive FSMs, using figure 15.3c as example. Its construction is similar to M2, but the timer is optional here. If the timer is needed, the choice is limited to T15 and T17 (because of the pr_state versus nx_state comparison).

Template T15: For recursive Moore + Mealy machines, with some outputs registered and no latency (4 modules)

It is highly modular, with separate processes for blocks R1, L1, L2″ + R2, and L2′. Notice in the process of lines 44–56 that the recursive signal (i) is registered, but it is based on nx_state (line 47) instead of pr_state, so a Mealy-type output results (recall the conclusion of section 15.3.2), thus making up for the latency. Observe also in the process of lines 59–69 that the other signal (j) was not registered, thus a Moore-type construction is being used.

Template T16: For recursive Moore + Mealy machines, with some outputs registered and no latency (2 modules)

T16 is equivalent to T15, just with a more compact construction. It is very important to observe that the registered output values are for the next state; for example, in line 14, the value listed for i (i = i + 1) is for state count (which is the next state, line 13), not for state zero (which is the present state, line 11).

Template T17: For recursive Mealy machines, with all outputs registered and no latency (3 modules)

T17 is derived from T15, just with the last two processes put together because both outputs must now be registered (similar to T9 if untimed). Once again, observe the use of nx_state instead of pr_state in line 47 (recall the conclusion of section 15.3.2).

Template T18: For recursive Mealy machines, with all outputs registered and no latency (1 module)

T18 is derived from T16 and is equivalent to T17 (and similar to T8 if untimed). Though its lack of modularity hides some structural details, it is simple enough and its low verbosity makes it very attractive. As in T17, observe that the listed output values are for the next state; for example, in lines 13–14, the values listed for i and j are for state count (which is the next state, line 12) and not for state zero (which is the present state, line 10).

Templates for recursive mixed Moore-Mealy FSMs with some outputs registered and no latency⁽*⁾

Templates for recursive Mealy machines with all outputs registered and no latency⁽*⁾

15.6    Summarizing (and Simplifying) Things

The previous sections presented a detailed study on templates for implementing FSMs using VHDL. Because the selection of the right template for the problem at hand might seem a bit complex, a global view is presented in this section, which will make such a decision quite straightforward.

The four general approaches covered in the templates are summarized in figures 15.4a to d. In (a), the FSM is implemented without the optional output register. In (b), such a register is included, and because the FSM’s outputs are stored in that register at the next clock edge, a latency of one clock period is added to the final signal. In (c), the outputs are again registered, but this time it occurs at the same clock edge that triggers the FSM, so latency does not occur. Finally, in (d), a mixed case is depicted where only some of the outputs are registered, but all are in phase and without latency.

The template selection can be made using the table of figure 15.4. After the state transition diagram has been drawn, the designer must do the following:

1)  Observe the machine’s category (regular, timed, or recursive). This will determine to which column (1, 2, 3.1, or 3.2) of the table in figure 15.4 the design corresponds.

2)  Decide whether in the target application the outputs should be registered, and if so decide whether the corresponding latency is acceptable or not. This will determine the right architecture from figures 15.4a to d, and therefore the right row in the accompanying table.

3)  The intersection of the row and column determined above will point to the suggested template.

For example, if the FSM is timed, does not have recursive outputs, and the outputs must be registered but without latency, we are in column 2 and in figure (c), so the suggested template is T14.

This template selection procedure will be used in the designs of chapter 16.

15.7    Exercises

Only three exercises were selected to be in this chapter because they are intimately related to the templates introduced here (all other exercises are in chapter 16). It is very important that the reader solve them to get fully acquainted with such templates.

Exercise 15.1. Checking the templates for regular (category 1) machines

This exercise concerns the design of the FSM of figure 15.1d using the conclusions of figure 15.4.

Part 1: For templates T1 and T2

a)  Make a sketch showing the waveforms that you expect T1 (of section 15.3.3) to produce. Include in it the plots for rst, clk, x, pr_state (or state), y, and z (in this order). Use the following waveform for x (with each value lasting one clock period, updated on negative clock edges, while the machine operates at the positive edge): "01010100100".

b)  Compile and simulate this code (T1) and check whether the results match your sketches above.

c)  Repeat part (b) using T2, and check whether the simulation results are still the same (as predicted in figure 15.4).

Part 2: For templates T3 and T4

d)  Redo the sketch of part (a), now using T3.

e)  Compile and simulate this code (T3), and check whether the results match your sketches above.

f)  Repeat part (e) using T4, and check whether the simulation results are still the same (as predicted in figure 15.4).

Part 3: For templates T9 and T8

g)  Redo the sketch of part (a), now using T9.

h)  Compile and simulate this code (T9), and check whether the results match your sketches above.

i)  Repeat part (h) using T8, and check whether the simulation results are still the same (as predicted in figure 15.4).

Exercise 15.2. Checking the templates for timed (category 2) machines

This exercise concerns the design of the FSM of figure 15.2c using the conclusions of figure 15.4.

Part 1: For template T10

a)  Based on what figure 15.4 says, when using template T10 (of section 15.4.2), do you expect the result for y to be like that for y or for y_reg of figure 3.16b?

b)  Add to figure 3.16b the plot you expect for z when using T10.

c)  Implement the FSM of figure 15.2c using T10 (enter Tdeb = 3 directly as a constant). Then simulate it using for x the same stimuli of figure 3.16b, and analyze the results. Do y and z match your prediction/sketch?

Part 2: For template T11

d)  Repeat the analysis of part (a), now using T11 instead of T10.

e)  Add to figure 3.16b the plot that you expect for z when using T11.

f)  Repeat the compilation and simulation of part (c) using T11 instead of T10.

Part 3: For template T14

g)  Repeat the analysis of part (a), now using T14 instead of T10.

h)  Add to figure 3.16b the plot you expect for z when using T14.

i)  Repeat the compilation and simulation of part (c) using T14 instead of T10.

Exercise 15.3. Checking the templates for recursive (category 3) machines

This exercise concerns the design of the FSM of figure 15.3c using the conclusions of figure 15.4.

Part 1: For template T8 (which is similar to T18)

a)  Based on what figure 15.4 says, when using template T8, do you expect the result for i to be like that for i or for i_reg of figure 3.20c?

b)  Add to figure 3.20c the plot you expect for j when using T8.

c)  Implement the FSM of figure 15.3c using T8 (for MAX = 5). Then simulate it using for ena the same waveform of figure 3.20c, and analyze the results. Do i and j match your prediction/sketch?

Part 2: For template T9 (which is similar to T17)

d)  Repeat the analysis of part (a), now using T9 instead of T8.

e)  Add to figure 3.20c the plot you expect for j when using T9.

f)  Repeat the compilation and simulation of part (c) using T9 instead of T8.

Note: See chapter 16 for additional examples and exercises on FSMs.

16    The Case of State Machines: Practice

Chapter 15 was dedicated to the more conceptual aspects of finite state machines (FSMs), leading to a collection of VHDL templates for implementing all sorts of machines. This chapter is dedicated entirely to practicing with such templates.

16.1    Design Examples of Regular (Category 1) State Machines

Example 16.1. ASCII-sequence detector

This example shows a VHDL implementation for the machine of figure 3.2b, which detects the sequence “abc” of ASCII characters. The first step (after having understood the problem well, drawn its state transition diagram, and concluded that it is a category 1 Moore machine) is to choose the template. Two conditions are assumed:

a) The output does not need to be registered: In this case, a pure Moore machine (default) is the preferred approach. It corresponds to figure 15.4a, and this is a regular machine, so column 1 of the table suggests that template T1 or T2 be used.

b) The output must be registered, but latency is undesired: This corresponds to figure 15.4c, so column 1 of the table suggests that template T8 or T9 be used (the former is usually preferred).

The code on the left below follows T1 exactly, while that on the right follows T8 exactly. The state machine view produced by the compiler (Quartus Prime was employed here) in either case is shown in figure 16.1a, in full agreement with our state diagram of figure 3.2b. Corresponding timing simulation results are shown in figure 16.1b, again exactly as expected. Observe that glitches can indeed occur (right after clock edges) in y from T1, while y from T8 is guaranteed to be glitch free.

Figure 16.1
ASCII-sequence detector of example 16.1: (a) State machine view produced by the compiler; (b) Timing simulation results from T1 (with glitch) and T8 (clean).

ASCII-sequence detector of example 16.1 (templates used: T1 on the left, T8 on the right)

Example 16.2. Garage door controller

This example shows a VHDL implementation for the machine of figure 3.22c, with registered outputs, which operates a garage door using signals from a remote control and from door-closed and door-open sensors. Since the latency introduced by the output register is certainly not an issue in this application, a Moore machine with registered outputs is fine. It corresponds to figure 15.4b, and it is a regular machine, so column 1 of the table in figure 15.4 suggests that template T3 or T4 be used. The code below follows the latter.

The state machine view produced by the compiler is shown in figure 16.2a, in full agreement with figure 3.22c. Timing simulation results are shown in figure 16.2b, again exactly as expected. Notice that two sets of outputs are shown; the first motor plus direction pair was obtained without the output register (i.e., with lines 64 and 91 commented out and the word clk in line 62 replaced with all). Notice that, as expected, the second motor plus direction pair is delayed by one clock period but is glitch free.

Figure 16.2
Garage door controller of example 16.2: (a) State machine view produced by the compiler; (b) Timing simulation results without and with the output register.

Garage door controller of example 16.2 (template used: T4)

16.2    Design Examples of Timed (Category 2) State Machines

Example 16.3. Password decoder

This example shows a VHDL implementation for the password decoder of figure 3.27, which is a timed Moore machine. To ease the demonstration (in the FPGA board), the keypad was replaced with three pushbuttons in figure 16.3. The password used here consists of three 3-bit digits, with values a = "011", b = "101", and c = "110". When none of the pushbuttons is pressed, the input is none = "111" (abbreviated as n in the state diagram of figure 3.27c). Notice that a multi-switch debouncer is needed (that of figure 2.30 will be adopted).

Figure 16.3
Password decoder of example 16.3.

The (optional) output register will be adopted in this case (see reasons for adding such a register in comment 7 of section 15.1). Since the latency due to such a register is definitely not a problem here, the architecture of figure 15.4b can be used. Moreover, this is a timed application, so column 2 of the table in figure 15.4 suggests that template T11 be used.

The code below follows T11 exactly. Notice that the chosen password (a-b-c) is specified via generic list (lines 7–10). The timer-related declarations are in lines 28–30; to span three seconds, with fclk = 50 MHz, the value of T_TIMER has to be 150M clock cycles. The reader is invited to examine the code carefully and compare it to figure 3.27c and template T11. Notice that if we did not need the outputs to be registered, all we would have to do is to eliminate lines 104 and 112 and replace the word clk with all in line 102. The debouncer (of figure 2.30 and exercise 13.15) is instantiated as a component in line 34.

The number of DFFs expected to be inferred by the compiler is the following: 29 for the debouncer, with Tdeb = 20ms, fclk = 50 MHz, and three inputs; plus 34 for the password decoder (figure 3.27c), with sequential encoding and registered outputs; thus totaling 63 flip-flops. The reader is invited to synthesize this code to check this number and also to play with this circuit in the FPGA board.

Password decoder of example 16.3 (template used: T11)

Example 16.4. SPI interface for an A/D converter

This example shows a VHDL implementation for a serial peripheral interface (SPI) for the MAX1118 device, which is a two-channel, 10-bit analog-to-digital (A/D) converter, described in section 3.20.3 (figures 3.28 and 3.29). The solution using a timed FSM (figure 3.29a) is presented here; the other solution (pointer-based, figure 3.29b) is treated in exercise 17.1.

Virtually any of the templates for timed machines can be used in this case, so the default (Moore, with unregistered outputs) will be employed. That is the case of figure 15.4a, and the machine is timed, so column 2 of the table in figure 15.4 suggests that template T10 be used. The only particularities are the processes in lines 22–33 and 96–103; the former produces the SPI clock (5 MHz), while the latter stores the data read from the A/D converter. Everything else is a straightforward application of T10. Notice that in spite of being long, the maximum modularity of this code makes it very simple to understand and debug. The reader is invited to play with this design using the setup of figure 3.28.

SPI interface for an A/D converter (ADC) from example 16.4 (template used: T10)

16.3    Design Examples of Recursive (Category 3) State Machines

Example 16.5. SRAM memory interface

This example shows an implementation for the SRAM interface of figure 3.30. The addr output, being recursive, must be registered. Because a single DFF is needed for WEn, we will register it too. This corresponds to figure 15.4c, and since the machine is recursive and does not involve time, column 3.1 of the table in figure 15.4 suggests that T8 (preferred) or T9 be used. The code below follows T8 exactly, so the entire machine is implemented by a single process. The inputs are all of standard-logic types, which is a recommended approach.

The state machine view produced by the compiler is shown in figure 16.4a and agrees with figure 3.30c. Corresponding timing simulation results are shown in figure 16.4b, in agreement with figure 3.30b.

Figure 16.4
SRAM interface of example 16.5. (a) State machine view produced by the compiler; (b) Timing simulation results.

SRAM interface of example 16.5 (template used: T8)

Example 16.6. Datapath controller for a serial multiplier

This example shows a VHDL implementation for the recursive FSM of figure 3.31c, which builds the control unit for a datapath that executes the multiplication of two non-negative BITS-bit values (BITS was called N in figure 3.31c).

The inputs are likely to be all synchronous in this kind of application. The recursive output (i) obviously needs to be registered, but the others, being synchronous, can only have glitches (if any) immediately after triggering clock edges, which is usually not a problem in this kind of application, so they will not be registered. This corresponds to figure 15.4d, and since the machine is recursive and does not involve time, column 3.1 of the table in figure 15.4 suggests that T6 (preferred) or T5 be used.

A solution following T6 is shown below. Notice that the assignment i =i is redundant (included only for clarity; recall that the unaffected keyword or the null statement would have the same effect). A simplified version for the second process is shown at right in lines 71–101, in which default values are specified at the beginning of the process (lines 75–80), so only the values that differ from those need to be informed subsequently.

Datapath controller for a serial multiplier of example 16.6 (template used: T6)

16.4    Exercises

Part 1: Exercises with Regular FSMs

Exercise 16.1. Checking the templates for regular (category 1) machines

See exercise 15.1.

Exercise 16.2. Manchester encoder

An IEEE Manchester encoder produces a low-to-high transition when the input is '1' or a high-to-low transition when it is '0', as illustrated in figure 16.5 for the sequence "01001", where each input value lasts two clock periods and dashed lines indicate “don’t care” values. Observe that an enable (ena) signal defines the extension of the data vector to be encoded and that the first valid bit is received at the same time that ena is asserted. Small propagation delays were included between clock transitions and corresponding responses to portray a more realistic situation. In your solution, dout must be guaranteed to be glitch free and its phase must be exactly as shown in figure 16.5.

Figure 16.5
Timing diagram for a Manchester encoder.

a)  Draw a state transition diagram for an FSM that implements this encoder.

b)  What is the category (regular, timed, or recursive) and type (Moore, Mealy, or mixed) of your machine?

c)  To which of figures 15.4a to d does it correspond? What is the suggested template?

d)  Complete the plot for pr_state (or state) in figure 16.5.

e)  Implement your solution using VHDL. Use only standard-logic types for the ports.

f)  Show simulation results using the same stimuli of figure 16.5 and compare the results.

Exercise 16.3. Flag monitor

a)  Develop a state transition diagram for an FSM that monitors a flag such that, if the flag remains constant within an arbitrary-length time window, the output copies (registers) the flag value. This is illustrated in figure 16.6; if flag_in has no transitions while window is high, flag_out gets the value of flag_in; otherwise, it keeps the value it had when the time window started. Assume that flag_in is synchronous to the system clock.

Figure 16.6
Timing diagram for a flag monitor.

b)  What is the category (regular, timed, or recursive) and type (Moore, Mealy, or mixed) of your machine?

c)  To which of figures 15.4a to d does it correspond? What is the suggested template?

d)  Implement it using VHDL. Use only standard-logic types for the circuit ports.

e)  Show simulation results using the same stimuli of figure 16.6 and compare the results.

Part 2: Exercises with Timed FSMs

Exercise 16.4. Checking the templates for timed (category 2) machines

See exercise 15.2.

Exercise 16.5. Pulse stretcher

A pulse stretcher is a circuit that adds a certain number (Tstretch) of clock periods to the original duration of received pulses. For example, say that we are given the specifications summarized in figure 16.7, showing that the (positive) pulses in pulse_in, which are synchronous to our clock and last at least Tclk, must be enlarged by Tstretch = 3 clock periods, producing pulse_out shown in the figure (notice that it must go up as soon as pulse_in goes up). Say then that we are asked to model such a circuit as a pure Moore or Mealy FSM (no output register).

Figure 16.7
Behavior of a desired pulse stretcher.

a)  What is the category (regular, timed, or recursive) and type (Moore, Mealy, or mixed) of your machine?

b)  To which of figures 15.4a to d does it correspond? What is the suggested template?

c)  Draw the corresponding state transition diagram.

d)  Implement it using VHDL. Use only standard-logic types for the circuit ports.

e)  Show timing simulation results using the same stimuli of figure 16.7. Include in it the plots for rst, clk, pulse_in, pr_state, t (timer), and pulse_out (in this order). Compare pulse_out to that in figure 16.7.

Exercise 16.6. Multi-input debouncer for non-concurrent switches

This exercise deals with another solution for exercise 13.14, now using the FSM approach.

a)  A possible solution is partially sketched in figure 16.8, where the input and output are 3-bit values and T represents the debounce time. Study this diagram and complete it. (Hint: Review the examples of figures 3.21 and 15.2.)

Figure 16.8
Partial state diagram for the triple-debouncer of exercise 16.6.

b)  The outputs must be registered (to prevent glitches), so to which of figures 15.4a to d does this FSM correspond? What is the recommended template (in the table of figure 15.4)?

c)  Implement it using VHDL. Use only standard-logic types for the circuit ports and adopt the same generic parameters as example 13.2. Include in your design the test circuit of exercise 13.12.

d)  Demonstrate it in the FPGA board using the same procedure as exercise 13.15.

Exercise 16.7. Playing with a seven-segment display

The circuit of figure 16.9 must produce a rotating movement on a seven-segment display (SSD) as follows: segment a must be lit during a time T₁; then a and b should be lit together during a time T₂; then b alone during T₁ again; next b and c together during T₂; and so on. Signal start, produced by a pushbutton, must toggle the movement between letting it run and stopping it. Signal direction, produced by another pushbutton, must toggle the direction of rotation between clockwise and counterclockwise. Finally, signal speed, produced by a third pushbutton, must cause the speed to change every time it is pressed by changing T₁ as follows: 200ms → 140ms → 100ms → 70ms → back to 200ms (T₂ = 35ms always).

Figure 16.9
Rotating SSD.

a)  Draw a state transition diagram for this FSM.

b)  Does the output (ssd) need to be registered? What is the category (regular, timed, or recursive) and type (Moore, Mealy, or mixed) of your machine?

c)  To which of figures 15.4a to d does it correspond? What is the suggested template?

d)  How many DFFs are necessary to implement it, assuming fclk = 50 MHz and sequential encoding for the FSM states?

e)  You are not asked to do it (though you are welcome to), but explain why all three pushbuttons require debouncing.

f)  Implement your FSM using VHDL. Enter fclk (call it F_CLK in the code) as a generic parameter and declare T1 and T2 as internal constants.

g)  Did the inferred number of DFFs match your prediction (for fclk = 50 MHz)?

h)  Demonstrate your solution in the FPGA board (use now fclk of your board).

Exercise 16.8. Residential alarm system

Figure 16.10 depicts a residential alarm system, consisting of a keypad (to enter passwords), a password decoder (as shown in the time diagram, it produces an enable pulse that lasts at least one clock period every time a valid password is entered), and the alarm controller. The purpose of this exercise is to design the alarm controller block. Assume the following:

Figure 16.10
Residential alarm system.

- The ena signal is synchronous, glitch free, and already available;

- The sensorsNT signal, already debounced, represents the set of all non-timed sensors, so it must cause siren = '1' immediately when asserted if the alarm system is armed;

- The sensorsT signal, also already debounced, represents the set of all timed sensors, with maximum exit and entrance times of Tout and Tin seconds, respectively (the person has Tout seconds to leave the property after the alarm is armed and Tin seconds to disarm the alarm when reentering the property, if detection occurs);

- Tout = 10s, Tin = 5s, and fclk (of your FPGA board) must be entered as generic parameters;

- Use sequential encoding for the machine states.

a)  Draw a state transition diagram for an FSM capable of solving this problem. It should not accept activation if sensorsNT = '1' when the password is entered (i.e., when the ena = '1' pulse is received).

b)  Implement it using VHDL. Use only standard-logic types for the circuit ports.

c)  Demonstrate your design in the FPGA board. Use toggle switches to produce ena, sensorsNT, sensorsT, and rst; use an LED to represent the siren.

Exercise 16.9. Residential alarm system with buzzer

This is an extension to exercise 16.8. Add a buzzer to it that is turned on/off with a frequency of 1Hz (0.5s on, 0.5s off) while the machine is in a timed state (i.e., during the time intervals Tout and Tin).

Part 3: Exercises with Recursive FSMs

Exercise 16.10. Checking the templates for recursive (category 3) machines

See exercise 15.3.

Exercise 16.11. Reference-value definer with embedded debouncer (a recursive timed FSM)

A reference-value definer with embedded debouncer was presented in section 3.21.3. The interesting aspect of that machine is that it involves both recursive outputs and timed transitions.

a)  Study the machine of figure 3.32b to make sure that it solves the problem at hand, making modifications if you find it necessary to do so.

b)  Implement it using VHDL. Use only standard-logic types for the circuit ports. Below is a suggestion for the constants to be used in this design:

c)  Demonstrate your solution in the FPGA board. Use an SSD to display ref.

Exercise 16.12. SRAM interface

Note:    This design is for a circuit that interfaces with an actual SRAM chip. The chosen device is the IS61LV25616 SRAM, from Integrated Silicon Solution Inc. (ISSI). However, your FPGA board certainly contains an SRAM device, so you can use that one to develop a similar application.

The pinout of the IS61LV25616 SRAM is depicted in figure 16.11a. It can store 262 kwords of 16 bits each, hence requiring an 18-bit address bus, A(17:0), and a 16-bit data bus, D(15:0). It uses five control signals, all active low, called CEn (chip enable), WEn (write enable), OEn (output enable), UBn (upper byte enable), and LBn (lower byte enable).

Figure 16.11
(a) SRAM pinout; (b) Timing diagram illustrating the write and read procedures; (c) Possible physical setup.

The memory-write procedure, based on the device’s datasheet, is illustrated in the left part of figure 16.11b. Notice that here the end of the WEn pulse coincides with the beginning of a new memory-write cycle. Observe also that the wr command lasts just one clock period, so the end of writing is determined by a predefined maximum address value, Amax. Since the largest read/write time parameter is 10ns, a clock not faster than 100MHz will do.

The memory-read procedure is illustrated in the right half of figure 16.11b. When the device is not in write mode (write is done with WEn low), it is automatically in read mode, so when the rd command occurs (which also lasts only one clock period), all that is needed is to sweep the memory addresses.

a)  Draw a state transition diagram for an FSM that can write to and read from this memory.

b)  Implement it using VHDL. The implementation can be based on figure 16.11c, which shows static values for all control signals except WEn. For instance, notice that only UBn = '1', so playing only with the upper byte is good enough. Include in your solution a test circuit (not shown in figure 16.11c) that writes data to the memory and then reads it out to make sure that the circuit works as expected. Note that the data bus is bidirectional.

c)  Demonstrate your solution in the FPGA board (with this SRAM or some other).

16.5    Exercises with SPI, I2C, and LCD Interfaces

See chapter 17.

17    Additional Design Examples

This chapter shows several additional/extended design examples using VHDL. Some are solved directly, while others are modeled first as finite state machines. The examples include SPI and I²C serial data communication interfaces, alphanumeric LCD drivers, and VGA plus DVI video interfaces. Several exercises are proposed subsequently.

17.1    Additional Design Examples

The following examples are presented next:

Example 17.1. SPI Interface for an EEPROM (with FSM)

Example 17.2. SPI Interface for an EEPROM (with Pointer)

Example 17.3. I²C Interface for an A/D Converter (with Pointer)

Example 17.4. I²C Interface for an A/D Converter (with Pointer Built with FSM)

Example 17.5 Digital Watch with Liquid Crystal Display

Example 17.6 VGA Video Interface for a Hardware-Generated Image

Example 17.7 DVI Video Interface for a Hardware-Generated Image

Example 17.8 TMDS 8B/10B Encoder

Such designs are supported by specific appendices, as follows:

Appendix F: SPI Interface

Appendix G: I²C Interface

Appendix H: LCD Interface

Appendix I: VGA Interface

Appendix J: DVI Interface

Appendix K: TMDS Link

Appendix L: Using PLLs with VHDL

Example 17.1. SPI interface for an EEPROM device (with FSM)

Appendix F summarizes how SPI works. It also introduces an example that employs an EEPROM device (FM25L512, from Ramtron). The purpose here is to draw a state transition diagram for a finite state machine (FSM) capable of communicating with that device and then write a VHDL code for it and test it in the FPGA board. It is assumed that five bytes must be written to and read from that device.

State transition diagram

A solution is shown in figure 17.1, following figures 4c and d of appendix F directly. The master (our circuit) must produce three (SCLK, SSn, and MOSI) of the four SPI wires (the fourth, MISO, is produced by the slave). Notice that all three signals are listed in all states of figure 17.1, despite the fact that “don’t care” values (−) are used in some states. SCLK takes the value of the reference SPI clock (called simply clk here, of up to 20 MHz) in all clocked states. It was chosen not to give preference to wr over rd or vice versa, so the machine only leaves the idle state when only one of them is high.

Figure 17.1
SPI interface for an EEPROM device (FM25L512).

When the machine moves from idle to WREN_OP, SSn is lowered, so the device is selected and writing can start. After write_data is concluded, the machine moves to wait_wr_low, where it waits until wr is lowered (if still high) before returning to idle; notice that the passing to idle brings SSn back to 1, deselecting the device. Analogous conditions occur in the lower loop (for reading). Notice the box under the read_data state; because in this state it is the slave who sends data, the master must either store it or pass it to another circuit.

Observe that this machine consists simply of two big loops responsible for writing to (the upper loop) and reading from (the lower loop) the EEPROM. Moreover, multiple bits must be (serially) transmitted or received in several states, causing them to be repeated several times, as indicated by the generic pointer x(). Consequently, this machine clearly falls in the class described in section 3.15, even needing a dual (or an extended single) timer in two states (write_data, read_data), which send or receive 5 × 8 bits. The dual-timer outputs are represented by i and j.

The opcodes, listed in figure 4a of appendix F, are 8 bits long. The memory words too are 8 bits long, while the memory address consists of 16 bits (2¹⁶ words). Writing and reading can consist of a single byte or can be continuous (the device automatically increments the memory address); since we want to write and read multiple bytes, the latter is the case here.

VHDL code and experiment

A code for the FSM of figure 17.1 is presented below. From a functional point of view, this application does not require the outputs of out circuit to be registered, so a pure Moore machine can be used to implement it (though, from a system perspective, registering signals that leave a device is not a bad idea). Consequently, it corresponds to figure 15.4a, and being timed, the suggested template is T10.

Observe the parameters declared in the generic list (lines 7–11). In the architecture body, the first process (lines 46–57) produces the SPI clock (clk_12MHz), with frequency 12.5 MHz, by dividing the system clock by four. The second process (lines 60–89) creates a dual timer, following the same principle shown for a single timer in the master template M2 (section 15.4.2). The third process (lines 92–99) creates the register for the FSM states (block R1 of figure 15.2b). The fourth process (lines 102–144) produces the combinational logic for the next state (block L1 of figure 15.2b). The fifth process (lines 147–180) completes the FSM by implementing the combinational logic for the outputs (block L2′ of figure 15.2b); note that default values are entered (lines 150–152) before the case statement. The sixth process (lines 183–190) stores the data received from the slave device (EEPROM). Finally, the seventh process (lines 193–212) provides a test circuit that outputs sequentially—at low speed (1Hz)—the data read from the EEPROM, so it can be displayed (on eight LEDs, for example) for visual inspection when playing with this circuit in the FPGA board.

SPI interface for an EEPROM (FM25L512) of example 17.1 using timed FSM (template used: T10)

Example 17.2. SPI interface for an EEPROM device (with pointer)

Appendix F summarizes how SPI works. It also introduces an example, employing an EEPROM device (FM25L512). A solution for that problem using a conventional FSM was presented in example 17.1. Since that machine falls in the class described in section 3.15, it is a good candidate for the pointer-based construction approach seen in section 3.16.

State transition diagram

A corresponding FSM, represented using the functional diagrams of figure 4 of appendix F directly, is shown in figure 17.2. It must write to and read from the EEPROM a total of five bytes of data, so the pointers’ (i, j) values are those written under each time slot (notice that the time slots for i = 44 in writing and i = 34 in reading are not really needed). Though two pointers were employed, we saw in section 3.15 that a single extended pointer would also do.

Figure 17.2
FSM for SPI communication with the FM25L512 EEPROM: (a) Write loop; (b) Read loop.

The read_data state in figure 17.2b is shown in gray to indicate that in that time slot it is the slave who transmits the data, which the master must then either store locally or pass to another circuit (the former will be adopted in the implementation). As will be seen in the VHDL code, it was chosen not to give preference to wr over rd or vice versa, so the machine only leaves the idle state when just one of them is high.

VHDL code

A code that implements the FSM of figure 17.2 using the pointer-based approach is presented below. Observe the parameters declared in the generic list (lines 7–11). In the architecture body, the first process (lines 44–55) produces the SPI clock (clk_12MHz), of 12.5 MHz. The second process (lines 58–89) implements the pointer, which is usually the tricky part in this design approach. The third process (lines 92–147) implements the lookup table (LUT) that produces the outputs. The fourth process (lines 150–159) stores the data received from the slave device (EEPROM). Finally, the fifth process (lines 162–181) provides a test circuit that outputs sequentially, at low speed (1Hz), the data read from the EEPROM, so it can be displayed (on eight LEDs, for example) for visual inspection when playing with this circuit in the FPGA board.

SPI interface for an EEPROM (FM25L512) of example 17.2 using pointer-based approach

Example 17.3. I2C interface for an A/D converter (with pointer)

Appendix G summarizes how I²C works. It also introduces an example using an analog-to-digital converter (ADC) device (MAX11647, from Maxim). The purpose here is to develop a state diagram for it and then write a corresponding VHDL code and test it in the FPGA board. Because the FSM for this problem falls in the class discussed in sections 3.15, it can be easily built using the pointer-based approach seen in section 3.16, which is the option adopted here (the solution using a conventional FSM is treated in exercise 17.4).

State transition diagram

An FSM for this problem, represented using the functional diagrams of figure 4 of appendix G directly, is shown in figure 17.3. Slots in gray indicate that it is the slave who is in control of SDA, so the master must retreat to the high-impedance condition ('Z'). The bit values in the slave address, setup register, and configuration register are those explained in appendix G. As will be seen in the VHDL code, it was chosen not to give preference to wr over rd or vice versa, so the machine only leaves the idle state when only one of them is high.

Figure 17.3
FSM for I²C communication with the MAX11647 ADC: (a) Write loop; (b) Read loop.

VHDL code

A code that implements the FSM of figure 17.3 using the pointer-based approach is presented below. Observe the parameters declared in the generic list (lines 7–9). In the architecture body, the first process (lines 25–36) produces the I²C clock (clk_400kHz). The second process (lines 39–68) implements the pointer, which is usually the tricky part in this design approach. The third process (lines 71–132) implements the LUT that produces the outputs. Finally, the fourth process (lines 135–148) stores the data received from the slave device (A/D converter). Contrary to examples 17.1 and 17.2, this code does not include a test circuit, which is left to the reader to implement.

I²C interface for the A/D converter (MAX11647) of example 17.3 using the pointer-based approach

Example 17.4. I2C interface for an A/D converter (with pointer built with FSM)

As seen, pointer-based implementations consist of two main parts: pointer(s) and LUT. The tricky part is the pointer because it must embed all machine transition conditions. Consequently, one might opt to use a conventional FSM to build the pointer(s) (not the complete circuit). In this case, the design can be divided as follows:

- Develop a code for an FSM that implements the pointer;

- Develop the main code, without the pointer(s), and instantiate in it (as a component) the machine above.

For the case in example 17.3, this implies in removing the process of lines 39–68 and adding the line below:

State machine for the pointer in example 17.3

Figure 17.4 shows an FSM that implements the pointer for the case in example 17.3. Notice that it includes all transition conditions (wr, rd). Observe also that it is a recursive machine (because we want i to be an output).

Figure 17.4
Pointer of example 17.3 implemented using a conventional recursive machine.

VHDL code

A code for this machine is shown below. Because it is a recursive machine and we want the outputs to be registered, but with no latency compared to a Moore machine without output registers (reference), the implementation has to be of Mealy type, thus corresponding to figure 15.4c. Since time is not involved, column 3.1 of the table suggests that template T8 (adopted) or T9 be used.

For clarity, the list of outputs is the same in all states, which is not necessary when the outputs are registered. This caused some redundant assignments (i = i) to result (recall that unaffected or null, seen in section 12.5, would have the same effect). Recall also that default values could have been used.

Pointer generation for the circuit of example 17.3 using the recursive FSM of figure 17.4

Example 17.5. Digital watch with liquid crystal display (LCD)

Figure 17.5 shows, on the left, a circuit that implements a digital watch, whose outputs are units and tens of seconds (secU, secT), units and tens of minutes (minU, minT), and units and tens of hours (hourU, hourT). The second circuit is an LCD driver that interfaces the digital watch with a 16 × 2 alphanumeric LCD (described in appendix H).

Figure 17.5
Digital clock with LCD display.

The digital watch is controlled by the four switches listed below; to save hardware, powers of two (simple shifts) were chosen as speed-up factors in the time-setting switches:

set_sec (adjustment of seconds): Increases the watch’s speed by a factor of 8 (8s/s).

set_min (adjustment of minutes): Increases the watch’s speed by a factor of 256 (4.3min/s)

set_hour (adjustment of hours): Increases the watch’s speed by a factor of 8192 (2.3h/s).

rst: Returns the digital watch to zero and the LCD interface’s FSM to its initial state.

The outputs of the LCD interface circuit are the signals already described in appendix H:

E (enable): Actual LCD clock.

RS (register select): Selects LCD instruction (1) or data (0) register.

R/W (read/write): Selects LCD read mode (1) or write mode (0).

DB (data bus): 8-bit data bus.

LCD_ON: Turns display on (1) or off (0).

BKLT_ON: Turns backlight on (1) or off (0).

State transition diagram

A solution for this problem is shown in figure 17.6, using a regular (category 1) FSM (the initialization sequence is exactly as seen in figure 4 of appendix H). The machine displays the watch’s digits, with tens and units of hours in the first two positions, then a colon, followed by tens and units of minutes, then another colon, and finally tens and units of seconds, after which the machine repeats the loop, overwriting the characters with the new readings. Notice that DB = 00111010 is used in states WriteColon1 and WriteColon2, which corresponds to the “:” character in the CGROM table of figure 3 of appendix H.

Figure 17.6
LCD display interface for a digital watch.

The conventional FSM approach will be employed to solve this problem, but observe that this FSM is just a big loop and thus is a good candidate for implementation using the pointer-based approach seen in section 3.16 (exercise 17.9).

VHDL Code

A complete VHDL code for the FSM of figure 17.6, with the initialization sequence shown in figure 4 of appendix H, is presented below. Since it is a regular (category 1) Moore machine, without registered outputs, it corresponds to figure 15.4a, for which the table suggests that template T1 or T2 be used; the code below follows a variation of those templates, constructed with two modules (processes), being one for the state register and the other for the entire combinational logic.

The entity, called digital_watch_with_LCD_display, is in lines 5–13. The clock frequency was entered as a generic constant (line 7), so the speed-up factors (lines 53–56), the 1-s time base for the clock (line 60), and the frequency of the LCD clock (500 Hz, lines 123 and 127) will adjust automatically when this parameter is modified. The circuit ports (lines 9–12) are exactly as in figure 17.5 and are all of standard-logic types.

The architecture body, called rtl, is in lines 15–209. Its declarative part (lines 16–47) starts with a function (lines 18–33) that converts digits from 0 to 9 to LCD characters according with the CGROM table of figure 3 in appendix H. It contains also FSM-related declarations (lines 36–40) and internal signal declarations (lines 43–46).

In the statement part of the architecture (lines 49–208), both circuits of figure 17.5 are implemented. The digital watch (lines 50–112) starts by defining the speed-up factors (for manual adjustment of hours, minutes, and seconds), followed by a set of counters that produce first a 1-second reference (count0) and then the six watch digits (count1 to count6).

The second circuit, the LCD interface (lines 114–207), starts by setting the fixed voltages for the LCD device (lines 117–119), followed by a process that produces the LCD clock (500 Hz, lines 122–132). The remainder of the code (lines 134–207) is a straightforward implementation of the FSM of figure 17.6 (a regular Moore machine using a modified version of templates T1 and T2 of section 15.3.3).

Note that the solution below is a good candidate for separation into two circuits, instantiated subsequently as components (section 10.5) in a structural (main) code, as in the DVI of example 17.7.

Digital watch with LCD display of example 17.5 using a regular FSM (template: modified T1/T2)

Example 17.6. VGA video interface for a hardware-generated image

The construction of VGA interfaces is summarized in appendix I. The main purpose of the example below is to illustrate how the control signals can be generated. To keep the focus on that, a simple image is used, so it can be generated by the FPGA itself. Other cases are treated in the exercises.

Circuit setup

The setup for the demonstration is shown in figure 17.7a (adapted from figure 2 of appendix I). The inputs are the system clock (assumed to be 50 MHz) plus three signals (R_switch, G_switch, and B_switch) produced by three switches. The outputs are the five fundamental signals needed by the monitor (see VGA cable), which include the three pixel signals (R, G, and B, in their analog versions) plus the horizontal and vertical synchronism signals (Hsync, Vsync). As indicated in the figure, the DAC employed here is the ADV7123 device, which is a triple 10-bit video DAC (its BLANKn and SYNCn pins were kept at VDD and GND, respectively).

Figure 17.7
(a) Setup for the experiment of example 17.6; (b) Time parameters for 640 × 480 × 60Hz VGA mode.

The intended operation is in the basic 640 × 480 × 60Hz VGA mode, so the time parameters are those shown in figure 17.7b (copied from figures 4 and 5 of appendix I).

The image to be generated consists simply of five horizontal stripes:

Line 1: Red (R = "11…1" → red analog voltage = 0.7V)

Lines 2–3 and 480: Green (G = "11…1" → green analog voltage = 0.7V)

Lines 4–6: Blue (B = "11…1" → blue analog voltage = 0.7V)

Other lines: Color determined by the three switches mentioned above. For example, if R_switch = '1', then R = "11…1"; else, R = "00…0".

VHDL code

A VHDL code for this VGA interface is presented below. The control signals’ parameters of figure 17.7b were entered as generics (lines 7–14), so the code can be easily adjusted to other VGA modes (except for the clock frequency). The port names (lines 16–21) are from figure 17.7a (BLANKn and SYNCn, being static, were not included in the figure).

The two FPGA circuits of figure 17.7a are constructed in lines 28–81 (control generator) and 83–119 (image generator). As we have seen, the first is application-independent, while the second depends on the image(s) to be displayed. The reader is invited to analyze the code using figure 17.7 and appendix I and physically test it in the FPGA board.

Example 17.7. DVI video interface for a hardware-generated image

The DVI standard for connecting computers to LCD displays is summarized in appendix J. A complete application example is presented here. A simple image is used, so it can be generated by the FPGA itself.

Circuit setup

The setup for the demonstration is shown in figure 17.8 (adapted from figure 2 of appendix J). It consists of a single-link DVI interface, now with the image generator and the control generator blocks included (as in figure 17.7). This division is important because not all subsections change from one application to another, so the fixed ones only need to be designed once (only the image generator portion is application-dependent in figure 17.8).

Figure 17.8
Complete single-link DVI interface.

Two obstacles must be overcome to do the experiments. The first is the absence of current-mode logic (CML) pins in FPGAs, which can be overcome with the pairs of 270-ohm resistors shown in all transition-minimized differential signaling (TMDS) outputs of figure 17.8, allowing regular 3.3V LVCMOS pins to emulate CML inputs and outpts (see why in part 3 of appendix K).

The second obstacle is the usually low speed limit of FPGAs, which can be overcome by operating at low resolution. If the basic 640 × 480 × 60Hz mode is employed, a 25 MHz clock (clk25) can be used, resulting in a bit rate of 250 Mbps, which can be handled by virtually any FPGA.

Since a phase locked loop (PLL) is needed to implement this interface (to produce the 250 MHz clock for the serializer), we can derive the pixel clock (25 MHz) too from the PLL (superior alignment), as shown in figure 17.8.

VHDL code

This design consists of several independent circuits, so it is a great candidate for structural implementation (the circuits are developed separately and are then put together using component instantiations, as described in section 10.5). A total of four different circuits is implemented by the next four files. The fifth file is the main project file, which instantiates each of these individual circuits one of more times, resulting in the complete structure of figure 17.8.

File 1: Image generator    As indicated in line 14 of the code below, this is exactly the same as the image generator used in the VGA interface of example 17.6.

File 1: Image generator

File 2: Control generator    Except for the presence of a PLL and different clock names, this code is equal to that developed for the control generator of the VGA interface of example 17.6. To declare the PLL (lines 26–31 below) and instantiate it (line 36), just follow appendix L. For the control generator proper, line 38 indicates that the same code used for the control generator in the VGA interface of example 17.6 can be used (change the clock name from clk_vga to clk25).

File 2: Control generator

File 3: TMDS encoder    This is another circuit developed in another example (example 17.8, explained in appendix K). As indicated in line 15, just copy the code from lines 15 to 110 of the TMDS encoder of example 17.8 (change the clock name from tmds_clk to clk25).

File 3: TMDS encoder

File 4: Serializer    This circuit converts a 10-bit signal, produced at the rate of 25 MHz, into a serial single-bit signal transmitted at 250 MHz, for which signals produced by a PLL are employed.

There are several ways of implementing a serializer (section 2.11.8; the option in figure 2.39c is adopted here). In the case below, the 10-bit input is first registered (lines 16–21), then the registered bits feed a 10-input multiplexer (line 33). The critical part is the construction of the port-select signal for the multiplexer (called i) because it requires some kind of synchronism between clk25 and clk250 (lines 26–32).

File 4: TMDS serializer

File 5: Main code    This file simply puts together the four designs presented above by means of component instantiations (the option of section 10.5.2 is employed), resulting in the structure of figure 17.8. The reader is invited to compile this project and play with it using the FPGA board and a DVI-interfaced monitor.

File 5: Main code (structural)

Example 17.8. TMDS 8B/10B encoder

The construction of TMDS links was summarized in appendix K. As seen there (in figure 1), the transmitter consists of an 8B/10B encoder plus a serializer. The purpose of this example is to implement the former.

A hardware implementation for the encoding algorithm of figure 3 of appendix K is depicted in figure 17.9. Except for the disparity (disp), which must be accumulated (so a register is needed), the entire algorithm is combinational. In the option of figure 17.9, the output (dout) was also chosen to be registered to provide a clean signal that remains static between one computation and the next. The actual output is selected by the multiplexer controlled by dena (display enable); when dena = '1', pixel data is sent out, while dena = '0' selects one of the control signals.

Figure 17.9
RTL implementation for the TMDS 8B/10B encoder.

A corresponding VHDL code is shown next. The circuit ports (lines 7–11) are those seen in figure 17.9. In the declarative part of the architecture (lines 15–33), a function was created (lines 22–32) to count the number of ones in a binary vector, so it can be used later to determine onesD (line 39) and onesX (line 61).

The code proper is divided in just two processes. The first (lines 37–63) determines the internal vector x, with 9 bits, while the second process (lines 66–109) determines dout and disp, both registered (note that both are under the if rising_edge() statement of line 68, which is only closed in line 108). Corresponding simulation results are shown in figure 17.10.

Figure 17.10
Simulation results from the 8B/10B TMDS encoder of example 17.8.

This circuit is used as part of DVI interfaces (example 17.7).

17.2    Exercises

Part 1: Exercises with SPI Protocol

Exercise 17.1. SPI interface for an A/D converter

An SPI interface for the MAX1118 device, which is a two-channel, 10-bit A/D converter, was described in section 3.20.3, and an implementation with VHDL, using a conventional (timed) FSM (figure 3.29a), was presented in example 16.4. After studying that material, complete the following items:

a)  Redo the design of example 16.4 now using the pointer-based approach (figure 3.29b).

b)  Demonstrate it in the FPGA board.

Exercise 17.2. SPI interface for a temperature sensor

This exercise deals with a temperature sensor that is accessed via SPI protocol. The suggested device is MAX31722 or similar.

a)  Carefully study the selected device and appendix F. Then devise an experiment for that device and explain it. Include a copy of the device’s top view and a sketch of the overall circuit (as in figures 3a and b of appendix F).

b)  Draw the state transition diagram of an FSM that implements your experiment (as in figure 17.1 or 17.2).

c)  Write a corresponding VHDL code and briefly explain it.

d)  Demonstrate your design in the FPGA board.

Exercise 17.3. SPI interface for an accelerometer

This exercise deals with an accelerometer that is accessed via SPI protocol. The suggested device is LIS3LV02 or similar (this device supports both SPI and I²C access).

a)  Study carefully the selected device and appendix F. Then devise an experiment for that device and explain it. Include a copy of the device’s top view and a sketch of the overall circuit (as in figures 3a and b of appendix F).

b)  Draw the state transition diagram of an FSM that implements your experiment (as in figure 17.1 or 17.2).

c)  Write a corresponding VHDL code and briefly explain it.

d)  Demonstrate your design in the FPGA board.

Part 2: Exercises with I2C Protocol

Exercise 17.4. I2C interface for an A/D converter

An I²C interface for the MAX11647 ADC was implemented in example 17.3 using the pointer-based approach. After studying example 17.3 and appendix G, complete the following items:

a)  Write a VHDL code that implements that I²C interface using the conventional (timed) FSM approach.

b)  Demonstrate your design in the FPGA board.

Exercise 17.5. I2C interface for an EEPROM

This exercise deals with an EEPROM that is accessed via the I²C protocol. The suggested device is the 24Cxx memory, where xx can be 01 (1kbytes), 02 (2kB), 04 (4kB), 08 (8kB) or 16 (16kB).

a)  Carefully study the selected device and appendix G. Then devise an experiment for that device and explain it. Include a copy of the device’s top view and a sketch of the overall circuit (as in figures G.3a and b of appendix G).

b)  Draw the state transition diagram of an FSM that implements your experiment (for example, as in figure 17.1 or 17.3—though the former is for SPI).

c)  Write a corresponding VHDL code and briefly explain it.

d)  Demonstrate your design in the FPGA board.

Exercise 17.6. I2C interface for a temperature sensor

This exercise deals with a temperature sensor that is accessed via the I²C protocol. The suggested device is MAX31725 or similar.

a)  Carefully study the selected device and appendix G. Then devise an experiment for that device and explain it. Include a copy of the device’s top view and a sketch of the overall circuit (as in figures G.3a and b of appendix G).

b)  Draw the state transition diagram of an FSM that implements your experiment (for example, as in figure 17.1 or 17.3—though the former is for SPI).

c)  Write a corresponding VHDL code and briefly explain it.

d)  Demonstrate your design in the FPGA board.

Exercise 17.7. I2C interface for an accelerometer

This exercise deals with an accelerometer that is accessed via the I²C protocol. The suggested device is LIS3LV02 or similar (this device supports both I²C and SPI protocols).

a)  Carefully study the selected device and appendix G. Then devise an experiment for that device and explain it. Include a copy of the device’s top view and a sketch of the overall circuit (as in figures G.3a and b of appendix G).

b)  Draw the state transition diagram of an FSM that implements your experiment (for example, as in figure 17.1 or 17.3—though the former is for SPI).

c)  Write a corresponding VHDL code and briefly explain it.

d)  Demonstrate your design in the FPGA board.

Exercise 17.8. I2C interface for a real-time clock (RTC)

This exercise deals with an RTC that communities using the I²C protocol. The device depicted below is the PFC8593 (figure 17.11a) from NXP, but you can use a similar RTC. This is a complete and relatively complex device (see its registers in figure 17.11b), with clock, calendar, timer, and alarm.

Figure 17.11
RTC device (a) pins and (b) registers; (c) Setup for the experiments.

A setup for the experiment is suggested in figure 17.11c. The inputs are the write (wr) and read (rd) commands, plus the traditional clock and reset. The output is divided in two sets; the first set contains the I²C signals (SCL and SDA, plus a chip-reset signal), while the second set is for displaying on six SSDs or some other display the information retrieved from the RTC. Our circuit must first write the time and date into the RTC and then read it continuously, thus operating as a high-precision clock.

The 16 registers of the PCF8593 RTC are detailed in figure 17.11b. Register 0 is used for setup information; register 1 stores subseconds, with units of subseconds in bits 3:0 and tens of subseconds in bits 7:4; register 2 stores seconds, with units of seconds (0 to 9 values) in bits 3:0 and tens of seconds (values from 0 to 5) in bits 7:4; and so on. In what follows, data should be written to registers 0 to 7, which comprise the clock and calendar, then registers 2 to 4—which contain clock information concerning seconds, minutes, and hours—should be read (thus the initial address for reading is different from that for writing).

a)  After studying that device (or a similar one) and appendix G, draw the state transition diagram for an FSM that solves this problem and briefly explain it.

b)  Implement it using VHDL. Use any approach you like but briefly explain it.

c)  Demonstrate your design in the FPGA board.

Part 3: Exercises with Alphanumeric LCD

Exercise 17.9. Digital watch with LCD display (pointer-based implementation)

Carefully study the design of example 17.5 and appendix H. Then:

a)  Redo the design of example 17.5 using the pointer-based approach (section 3.16).

b)  Demonstrate it in the FPGA board.

Exercise 17.10. Blinking “VHDL” word

a)  Using VHDL, implement a circuit that displays the word VHDL on an alphanumeric LCD display (appendix H), blinking at 1Hz (0.5s on, 0.5s off).

b)  Demonstrate it in the FPGA board.

Exercise 17.11. Display your name on an alphanumeric LCD

a)  Write a VHDL code that implements a circuit that displays your name on an alphanumeric LCD (appendix H). Every 0.5s, one letter of your name must be shifted in, from left, as illustrated in figure 17.12, causing a sliding movement to the right.

Figure 17.12
Displaying your (sliding) name on an LCD.

b)  Demonstrate it in the FPGA board.

Part 4: Exercises with VGA Video Driver

Exercise 17.12. Hardware-generated image with VGA monitor (banner)

a)  Design a circuit capable of producing the image (banner) of figure 17.13a, displayed on a computer monitor with VGA support, operating in the 640 × 480 × 60Hz mode (or some other mode, if you prefer). The color on the left half must be determined by three toggle switches (for R, G, B), with the complementary color (R′, G′, B′) automatically assigned to the right half.

Figure 17.13
VGA images for exercises 17.12 and 17.13.

b)  Demonstrate your design using the FPGA board.

Exercise 17.13. Hardware-generated image with VGA monitor (rotating bars)

a)  Design a circuit capable of producing the image (rotating bars) of figure 17.13b, displayed on a computer monitor with VGA support, operating in the 640 × 480 × 60Hz mode (or some other mode, if you prefer). As soon as the green bar reaches the bottom, it must start reentering at the top; the same should occur with the red bar in the horizontal direction. The speed must be set by two toggle switches such that the approximate time it takes for the green bar to complete one screen is 8s (for speed = 00), 4s (speed = 01), or 2s (speed = 11). Choose for the red bar a speed such that it takes the same amount of time as the green bar to cover one screen.

b)  Demonstrate your design using the FPGA board.

Exercise 17.14. Digital clock with VGA monitor

a)  Design a circuit that implements a digital clock similar to that in example 17.5 but now with the output displayed on a computer monitor with VGA support, operating in the basic 640 × 480 × 60Hz mode (as in example 17.6). Place the digits in the positions given in figure 17.14.

Figure 17.14
VGA image of digital clock for exercise 17.14.

b)  Demonstrate your design using the FPGA board.

Exercise 17.15. Display your picture on a VGA monitor

a)  Design a circuit capable of displaying a picture of yours on a computer monitor with VGA support. Devise first a way of handling that image, which can be stored, for example, in a RAM device available in the FPGA board or in the FPGA itself (internal RAM memory), if there is enough room for that.

b)  Demonstrate your design using the FPGA board.

Part 5: Exercises with DVI Video Driver

Exercise 17.16. Hardware-generated image with DVI monitor (banner)

Solve exercise 17.12 using a DVI-interfaced monitor.

Exercise 17.17. Hardware-generated image with DVI monitor (rotating bars)

Solve exercise 17.13 using a DVI-interfaced monitor.

Exercise 17.18. Digital clock with DVI monitor

Solve exercise 17.14 using a DVI-interfaced monitor.

Exercise 17.19. Display your picture on a DVI monitor

Solve exercise 17.15 using a DVI-interfaced monitor.

18    Introduction to Simulation with Testbenches

This book is dedicated to design and synthesis, so this chapter is just a glimpse at what simulation with VHDL testbenches looks like. Nevertheless, it clearly exposes fundamental techniques that are needed when VHDL must be employed for simulation, like how to deal with time, how to deal with files, how to generate basic stimuli, how to analyze responses, how to assemble codes for both functional and timing simulations, and so on. Moreover, combined with the tutorial on ModelSim (appendix C), plus Vivado (appendix A) and Quartus Prime (appendix B), it allows readers to immediately start experimenting with testbenches, producing quite interesting (and complex) full verifications.

18.1    Testbenches

Testbenches are VHDL codes for circuit verification. The overall organization is depicted in figure 18.1 and consists of four blocks. The design under verification (DUV) is the (synthesizable) VHDL code of the designed circuit. It is also identified as design under test (DUT), though this designation is more appropriate for physical (chip) testing. The stimuli generator produces the input (excitation) signals, which can consist of data stored in local or file-based lookup tables (LUTs), time-dependent waveforms, randomly generated test vectors, and so forth.

Figure 18.1
Design verification with VHDL testbench.

The last two blocks form the response analyzer. The template generator provides the expected values, again expressed by means of LUTs or waveforms and possibly with the help of external data files. The comparator, which is constructed using assertion statements, is responsible for comparing the responses against the expected values, producing pass/fail results plus other pieces of information that are particularly important when the design fails.

The stimuli generator must be synchronized to the template generator. As illustrated in figure 18.1, that can be achieved, for example, by means of a test index (for testbenches based on LUTs) or by using time as reference (for testbenches based on waveforms).

Several variations are possible in the arrangement of figure 18.1. The main variations are listed below.

Case 1: Testbench without self-checking capability. In this case, only the stimuli generator and the DUV blocks of figure 18.1 are present. Because the results must be visually inspected, this kind of testbench is fine only for testing particular points of simple circuits or for instructional purposes.

Case 2: Complete testbench, with all blocks of figure 18.1 (hence including automated verification), with the stimuli plus the expected values combined in a LUT, which is installed in the testbench itself (in the declarative part of the architecture) or in an external text file. This kind of testbench, typical of combinational circuits, will be illustrated in examples 18.2 and 18.4.

Case 3: Complete testbench, similar to case 2, but with verification based on time-dependent waveforms rather than static values (LUT). This case, which is typical of sequential circuits, will be illustrated in example 18.3.

Besides the testbench architecture, another fundamental definition is whether it should be a functional or a timing simulation. In the former, only the DUV file plus the testbench file are needed. However, in the latter, the DUV file must be compiled differently, such that the simulator gets information regarding the circuit’s propagation delays (details on this will be seen in section 18.5).

Finally, recall the recommendation made in section 5.8.7 regarding good names for VHDL files. The testbench file will be saved with the same name given to the design file with the suffix _tb added to it; for example, if the DUV’s file is called adder_array.vhd, the testbench file will be named adder_array_tb.vhd.

18.2    Dealing with Time in VHDL

A major difference between a code for synthesis and one for simulation is the presence of time in the latter. Therefore, before we discuss the construction of testbenches, it is important to introduce some language elements that are fundamental for dealing with time and with simulation in general in VHDL; they are the wait for statement; the after clause; the now function; the stable, last_event, and transaction attributes; the assert and report statements; and the to_string function.

The wait for t statement    It causes the process to wait for t time units before proceeding. The time scale is not continuous (i.e., the value of t is not counted from time zero but rather from the point where the wait for statement is found), and it is implicitly looped unless it ends with a simple wait statement. In the example on the left below, a clock waveform with period 20ns is produced, while the code on the right produces a pulse called start with duration of 30ns low, then 20ns high, and finally low forever (it is not periodic because of the wait statement at the end).

The after t clause    Its behavior is very different from the wait for statement. It causes an action to be scheduled for the time value t in the time scale starting from time zero. Moreover, after clauses can (and should) go in concurrent code and are not implicitly looped. The two examples below produce the same waveforms as the two examples above.

The now function    It returns the current simulation time. In the example below, the process waits until the time 50ns is reached before assigning the value of x to y.

The S´stable(t) attribute    This attribute, listed earlier in table 9.9, returns true if no event has occurred on signal S during the last t time units (note that it looks backward). It is helpful, for example, for catching unwanted changes (signal transitions or glitches). In the example below, it inspects the last “T_CLK−5” nanoseconds.

The S´last_event attribute    This attribute, also listed earlier in table 9.9, returns the amount of time since the last event occurred on signal S. In the example below, it is part of an if statement.

The S´transaction attribute    As seen in table 9.9, this attribute causes a bit to toggle when a transaction is scheduled for signal S. This allows one process to communicate with another, helping create more modular testbenches. In the example below, this attribute is used in the communication between the stimuli generator and the template generator blocks of figure 18.1. An actual application will be shown in example 18.2.

Assertion statements and the to_string function    Assertions (section 14.8) are indispensable for building the comparator block of figure 18.1. Additionally, to construct dynamic assertions, the concatenation operator (&; see section 9.1.5) and the to_string function (seen in section 14.8) are also needed. Obviously, here we are interested mainly in using to_string for getting a response’s actual value, plus its expected value and the time value at the point where the sample was taken. Three general examples are shown below.

18.3    Stimuli Generation

Having seen how to deal with time in VHDL, we can now describe options for building the first block (stimuli generator) of figure 18.1 using time-dependent waveforms (dynamic stimuli). To illustrate it, the waveforms of figure 18.2 will be created; these consist of a periodic regular signal (clk), a single-pulse signal (rst), a nonperiodic irregular signal (x), a periodic irregular signal (y), and a multi-bit nonperiodic irregular signal (z). In simulation, contrary to synthesis, default (for signals) and initial (for variables) values are taken into consideration.

Figure 18.2
Examples of stimuli waveforms.

Generation of clk of figure 18.2: Both options below produce a 50MHz clock, with the initial value (zero) set in the signal declaration. The option on the right must be placed inside a process because the wait statement (section 12.5) is employed.

Generation of rst of figure 18.2: Two equivalent options are shown below. This time, the signal on the left was not initialized in its declaration.

Generation of x of figure 18.2: Three equivalent options are shown below.

Generation of y of figure 18.2: We can use any of the last two options above with the final wait statement removed.

Generation of z of figure 18.2: Two equivalent options are shown below.

Example 18.1. Stimuli generator

The goal of this introductory example is just to illustrate how the stimuli generator block of figure 18.1 can be implemented when waveforms are employed (complete testbenches will be seen in the next section). In this example, the waveforms of figure 18.3a will be produced. Note in the code below that the entity (lines 5–6) is empty, which is always the case for simulation unless generics are included in it. In the declarative part of the architecture body (lines 9–14), the signals representing the stimuli to be generated are declared and initialized (lines 11–14), along with two constants (lines 9–10) that allow the construction of a parameterized code. In the statements part of the architecture (lines 16–30), the stimuli are generated. The first three signals are built using the after clause (lines 17–19), which is the simplest option, so it should be used whenever possible. The final signal requires a counter, for which a loop is used, with the wait for statement employed for dealing with time. The waveforms produced by a simulator (ModelSim, described in appendix C) after processing this code are shown in figure 18.3b.

Figure 18.3
(a) Stimuli to be generated in example 18.1 and (b) corresponding experimental results.

18.4    Complete Testbenches

This section shows the construction of complete testbenches (thus with all blocks of figure 18.1 included). A combinational circuit is treated in the first example, while a sequential circuit is used in the second.

Example 18.2. Binary-to-gray converter (a combinational circuit)

This example shows the development of a complete testbench for a binary-to-gray converter (figure 18.4a), which is simple enough, so we can concentrate on the simulation procedure. Because this is a combinational circuit, the stimulus/expected response pairs of values can be stored in a simple table. Therefore, this example falls in case 2 of section 18.1.

Figure 18.4
Binary-to-gray converter of example 18.2.

The expected results (for 3-bit input, based on figure 2.14a) are listed in the table of figure 18.4b and can also be seen in the timing diagram of figure 18.4c. Note in the latter the inclusion of gray shades, representing the maximum acceptable delay (MAX_DELAY) and assumed to be defined in the specifications as 10ns (this is needed only for timing simulation). Therefore, if we are doing a timing simulation, we must sample the response 10ns after the input has changed to compare it to the expected value. Obviously, during the next 90ns, the output is expected to remain stable.

The first of the two codes below, called bin_to_gray, is for the DUV (therefore, synthesizable). It employs the approach seen in figure 2.14b for making the binary-to-gray code conversion.

The second code, called bin_to_gray_tb, is the testbench. The architecture’s declarative part (lines 9–29) contains general constant declarations in lines 10–12, followed by interface signal declarations in lines 14–16, and the LUT in lines 26–28, but note that it takes indeed lines 19–28 to construct the LUT, being a record employed for that. The constants (lines 10–12) inform the number of bits at the input and output, the test sampling time interval (100ns), and the maximum acceptable delay (10ns).

The architecture’s statement part (lines 31–50) starts with the DUV instantiation in line 32, followed by two processes that implement the stimulus generator (lines 34–41) and the response analyzer (lines 43–49). Note that these two processes communicate via the template table index (exactly as in figure 18.1), produced by the first process in line 36 and received by the second process in line 44 using the transaction attribute, which detects changes in the index value. If no error is detected, the first process runs to completion, so the message in line 40 is issued; otherwise, the second process interrupts the verification when the error is detected and issues the message of line 47, which includes the value of the template index at which the error occurred.

Functional versus timing simulation: This code can be used for both functional and timing simulations. In the former, only the two codes shown below are needed. For the latter, the DUV file must be replaced with a DUV file containing propagation delays information as explained in the next section. Simulation results obtained in both cases (with ModelSim, appendix C) are shown in figure 18.5 (observe the occurrence of delays for signal gray in the second graph).

Figure 18.5
Functional and timing simulation results from example 18.2.

Example 18.3. Registered multiplexer (a sequential circuit)

This is a sequential circuit, so a closer relationship with time is usually involved. The self-verification is based on waveforms and hence falls in case 3 of section 18.1.

The circuit, called registered_mux, is shown in figure 18.6a and consists of a 4 × 4 (four inputs of four bits each) multiplexer followed by a 4-bit (four DFFs) register. The stimuli to be applied to this circuit (clk, a, b, c, d, sel) are shown in the first six plots of figure 18.6b, from which the multiplexer output (mux) and the registered output (rmux) depicted in the last two plots are expected. The gray shades indicate again the maximum acceptable propagation delay (MAX_DELAY), assumed to be 10ns in the specifications. Note that the clock period is 100ns. In what follows, only the final output (rmux) will be checked; therefore, the comparisons should be made 10ns after each positive clock transition, as highlighted in figure 18.6c. The output is then expected to remain stable until the next rising clock edge occurs (that is, during the next 90ns).

Figure 18.6
Registered multiplexer of example 18.3.

The first of the two codes below, called registered_mux, is for the DUV (and is hence synthesizable). Again, the circuit is simple enough such that we can concentrate on the simulation procedure.

The second code, called registered_mux_tb, is the testbench. As usual, the entity (lines 5–6) is empty (because generics are not used). The architecture’s declarative part (lines 9–17) contains a general constant declaration in line 9 (which informs that the maximum allowed propagation delay is 10ns), followed by interface signal declarations in lines 10–17.

The architecture’s statement part (lines 19–62) starts with the DUV instantiation in line 21, followed by the stimuli generator in lines 24–29, the template generator in lines 32–40, and the comparator in lines 43–61 (exactly as in figure 18.1).

The stimuli generator (lines 24–29) is constructed using only the after clause (so waveforms are used now instead of a table with input/expected output pairs); observe that these signals are direct translations of the plots seen in figure 18.6b. The template generator (lines 32–40) is also constructed using only the after clause, hence again waveforms are involved. Finally, the comparator (lines 43–61) is built using a process because the wait statement is employed, with the results reported, as usual, using an assert statement.

The simulation is supposed to run from 0 to 1000ns (line 46), but, because the register’s output is unknown before the first (positive) clock transition occurs and the clock period is 100ns, the first comparison is only made when the time reaches 110ns, then being repeated after every 100ns. Observe also that the assertions now include a stable attribute (line 53), which checks whether the response stays constant in the 90ns interval prior to every (positive) clock edge (checking for glitches, for example). There are many ways of ending a simulation; in this case, the wait in line 59 is just to prevent the message in line 58 from being repeated, if the end time set in the simulator is above 1000ns.

If an error is detected, a message similar to this is issued:

Otherwise, the following message is issued:

Experimental results from the timing simulation above are shown in figure 18.7. Notice that there are no delays between changes in the stimuli and in rmux_expected, but there are delays between the stimuli and the actual output, rmux. However, because these delays are under 10ns in this particular implementation, errors do not occur. (Further details about timing simulation are given in section 18.5.)

Figure 18.7
Timing simulation results from the circuit of example 18.3.

18.5    Practical Considerations on Functional and Timing Simulations

Say that our design (DUV) file and our testbench file are called mydesign.vhd and mydesign_tb.vhd, respectively. These two are the only files needed to perform a functional simulation. The simulator does not need information from any synthesis/implementation tool because propagation delays are not taken into account.

To perform a timing simulation (as in examples 18.2 and 18.3), however, information on propagation delays must be provided by the synthesis/implementation tool. If the simulator and that tool are not integrated, the user must inform the synthesizer which third-party tool will be used for simulation, so the right modified version of mydesign.vhd will be produced along with an SDF (standard delay format) file.

As an example of timing simulation, say that Quartus Prime is used for synthesis and ModelSim is used for simulation. When the former processes mydesign.vhd, the two extra files just mentioned are generated under the names mydesign.vho (modified design file, containing propagation delays) and mydesign_vhd.sdo (standard delay format [SDF] file). These files must be copied to the ModelSim work directory, so timing simulation can be performed. In summary, three files are needed to perform a timing simulation in this case: the two just mentioned plus the testbench file, mydesign_tb.vhd. For further details, check the ModelSim tutorial in appendix C.

18.6    Dealing with Data Files

This section illustrates how data files can be used in simulation. We start by showing the main functions and procedures employed for reading from and writing to files, followed by a complete testbench that reads the stimulus and expected response from one file, applies them to the DUV, and then stores the result in another file.

Reading data from files

Two options are shown below for declaring a file and opening it in read mode; external_name is the actual file name, while file_identifier is how the corresponding file object will be identified internally.

Two options are shown next for reading a file after it is opened. First, one line is read by the readline procedure, and its content is passed to the line object identified by line_identifier. Next, the read procedure extracts each piece of data (blank spaces are ignored) one at a time from that line and passes them to the identified variables. The optional good_value field produces a boolean value that informs whether the type of the read data is compatible with the type of the variable to which the data is being assigned. The type of the variables can be time or any of those listed in table 7.2, with the exception of boolean_vector, integer_vector, and real_vector.

Finally, the function below is used for closing the file.

Example: The textio package, needed for dealing with files, appears in line 1 of the example below. In line 3, a file is declared; its actual (external) name is template.txt, which will be referred to internally as template_file. In this example, the file declaration is also responsible for opening the file in read mode. In line 5, the endfile function is employed to control the loop statement. In line 6, the readline procedure is employed to read one line of the file and pass its contents to a variable called current_line. In line 8, the first piece of data is separated from the line and is assigned to a variable called var1. In lines 9–11, type compatibility between var1 and the data just extracted from the line is tested. In lines 13–16, a sequence similar to that in lines 8–11 is performed for the second piece of data extracted from the line. Finally, in line 18, the loop is ended and in line 19 the file is closed.

Writing data to files

Two options are shown below for opening a file in write mode. Again, external_name is the actual file name and file_identifier is how the corresponding file object is identified internally. The file is automatically erased by the simulator before anything is written to it. To write data to a file without erasing it, the file must be opened in append_mode.

Writing to a file can be done as shown below. First, all pieces of information are written to a line using the write procedure (blank spaces must be inserted). Next, that line is written to the file using the writeline procedure.

For closing the file, the function below is again used.

The next example shows a complete testbench in which the stimulus and expected values are read from one file and the results are stored in another file.

Example 18.4. Binary-to-gray converter (using data files)

This example shows another solution for the problem of example 18.2, this time using data from/to files (so this example too falls in case 2 of section 18.1). The DUV’s code is obviously still the same, while the testbench code is shown below. The input file (template.txt), containing the stimulus (first column) and expected response (second column), is shown in figure 18.8a, while the file produced by the simulator (results.txt), containing the verification results, in shown in figure 18.8b.

Figure 18.8
Data files used in the testbench of example 18.4.

As usual, the declarative part of the architecture body (lines 10–17) contains declarations for all interface signals, including also the input and output files (line 16–17), and constants that express the time interval between consecutive samples (TEST_PERIOD = 100ns) and the maximum acceptable delay (MAX_DELAY = 10ns). Note the inclusion of the textio package (line 4) in the packages list.

In the statements part of the architecture (lines 19–81), the four blocks of figure 18.1 are distributed as follows: the DUV is in line 21, and the other three blocks are constructed in the single process of lines 24–80 (so the modularity of this solution is low). The file called template.txt, identified locally as template_ file and declared and opened in read mode in line 16, is read and checked in lines 37–49, with its stimulus values passed to signal bin in line 43, and its expected values (template) passed to signal gray_expected in line 49. The comparison occurs is in lines 53–57. The remaining lines are used for writing the results to the results.txt file, identified locally as results_ file and declared and opened in write mode in line 17. Both files are finally closed in lines 75–76. Notice that the file reading and file writing operations follow exactly what was explained before this example.

18.7    Running Simulation with Tcl Scripts

Simulations can also be run using tool command language (Tcl, pronounced “tickle”) scripts. Examples of such commands and scripts are presented in appendices A (for Vivado) and C (for ModelSim).

18.8    Exercises

Exercise 18.1. Signed adder

Develop a full testbench (see examples 18.2 to 18.4) for the signed adder of example 10.5. Adopt the same stimuli of figure 10.3b, and then comparing the obtained responses against those in the figure. Assume a maximum acceptable delay of 10ns.

a)  Do the exercise for functional simulation.

b)  Redo the exercise, now for timing simulation.

Exercise 18.2. Sine calculator

Develop a full testbench (see examples 18.2 to 18.4) for the sine calculator of example 11.3. Adopt the same stimulus of figure 11.5, and then compare the obtained response against that in the figure. Assume a maximum acceptable delay under 40ns.

a)  Do the exercise for functional simulation.

b)  Redo the exercise, now for timing simulation.

Exercise 18.3. Sequential square-root calculator

Develop a full testbench (see examples 18.2 to 18.4) for the sequential square-root calculator of example 13.4. Start by sketching and explaining the stimuli that will be applied during the tests and the expected responses. Assume that the specifications say that the time difference between changes in done or y and the corresponding triggering clock edge should not exceed 15ns.

a)  Do the exercise for functional simulation.

b)  Redo the exercise, now for timing simulation.

Exercise 18.4. Pulse width modulator (PWM)

a)  Solve exercise 13.17 in case it was not solved yet. A general view of the expected results is shown in figure 18.9.

Figure 18.9
General view of expected results in exercise 18.4.

b)  Develop a full testbench for it, using the same stimuli of part (c) from that exercise.

c)  Run your testbench for the case of functional simulation. Show the results obtained, and compare them to those obtained in exercise 13.17.

d)  Redo the exercise for timing simulation, with glitch verification included (use the stable attribute; see figure 18.9).

Appendix A: Vivado Tutorial

1.   Introduction

This tutorial is based on Vivado HLx 2018.2 WebPACK (free at xilinx.com). The circuit used in the tutorial is the registered unsigned adder of figure A.1a, synthesized with the VHDL code of figure A.1b. The adder inputs (a, b) are 3-bit signals, while its output (sum) is a 4-bit signal, so overflow never occurs. Both functional and timing simulations are shown using the stimuli of figure A.1c and in the following two situations: using a testbench (figure A.1d) and using a Tcl script (figure A.1e).

Figure A.1

The structure of the work library created by Vivado is shown in figure A.2. The registered_adder.srcs folder contains all source files (files created by the user), divided into three categories: sources_1 (design files), sim_1 (simulation files), and constrs_1 (constraint files). Note the file called registered_adder.xpr, which is the Xilinx project file; clicking on it opens the project.

Figure A.2

2.   Starting a New Project

a)  Launch Vivado, which opens the screen of figure A.3a.

Figure A.3

b)  Click Create Project and Next, which leads to figure A.3b. Enter the project name (registered_adder) and the desired location for the project. Mark Create project subdirectory and click Next.

c)  In figure A.3c, mark RTL Project (VHDL, in our case) and Do not specify sources at this time, then click Next.

d)  In figure A.3d, select the FPGA device or the FPGA board. In this tutorial, the XC7A35TCPG236 Artix-7 FPGA is employed. Click Next and Finish, which finally opens the project Flow Navigator (figure A.4).

Figure A.4

Note:   The FPGA selection can be made or changed later at PROJECT MANAGER > Settings.

e)  Observe on the lefthand side of figure A.4 the several sections of the Flow Navigator: PROJECT MANAGER, IP INTEGRATOR, SIMULATION, RTL ANALYSIS, SYNTHESIS, IMPLEMENTATION, and PROGRAM AND DEBUG (compare to the design flow described in chapter 5).

3.   Entering (and Testing) the Design File

Here, we must enter our VHDL design file (registered_adder.vhd, figure A.1b) The compiler will check the syntax and compile the code at register transfer level (RTL) level (no synthesis or placement yet), subsequently showing the corresponding elaborated design (i.e., the circuit, as understood from the VHDL code). The resulting schematic is equivalent to RTL View in Quartus Prime.

a)  Under PROJECT MANAGER, click Add Sources, which opens the window of figure A.5a. Mark Add or create design sources and click Next.

Figure A.5

b)  In the next screen, click Create File (or click Add Files if the file is already available).

c)  In figure A.5b, select VHDL and enter the file name (registered_adder), then click OK.

d)  In figure A.5c, enter the entity name (registered_adder) and the architecture name (rtl). Click OK and then Finish.

e)  In figure A.5d, note that registered_adder … is included in the Design Sources list and in the Simulation Sources list. Double click the former, which opens the editor (figure A.5f). Type the VHDL file (registered_adder.vhd, figure A.1b) and save it by clicking .

f)  In figure A.5e, open the General tab and select Type: VHDL 2008.

g)  A very important feature of Vivado is that errors in the code of nonsupported VHDL constructs are underlined in red. Introduce an intentional error in the code to observe that.

h)  RTL Analysis: We can now check how our code was understood by Vivado. Under RTL ANALYSIS, click Open Elaborated Design; when done, click Schematic. The resulting RTL view is shown in figure A.6, which matches the circuit of figure A.1a.

Figure A.6

4.   Doing Behavioral Simulation with Testbench

Notes:

1)  Recall that functional simulation, still at the RTL stage, is called behavioral simulation. After Synthesis or after Synthesis plus Implementation, its equivalent is called functional simulation. Timing simulation only exists for the latter two cases.

2)  In simple designs, one might opt for skipping behavioral and even functional simulation. Timing simulation is always indispensable.

3)  The simulation described here uses a VHDL testbench (see chapter 18; the testbench file is that of figure A.1d). Another option, described in the next section, is to use a Tcl script.

a)  The first step is to enter the VHDL testbench file. Under PROJECT MANAGER, click Add Sources, which opens the window of figure A.5a. This time, mark Add or create simulation sources and click Next.

b)  In the next screen, click Create File (or click Add Files if the file is already available).

c)  In figure A.5b, select VHDL and enter the file name (registered_adder_tb.vhd), then click OK.

d)  In figure A.5c, enter the entity name (registered_adder_tb, figure A.1b) and the architecture name (testbench). Click OK and then Finish.

e)  In figure A.5d, note in the Sources pane that registered_adder_tb - … is added to the Simulation Sources list. Double click it, which opens the editor (figure A.5f). Type the testbench file (registered_adder_tb.vhd, figure A.1d) and save it by clicking .

f)  In figure A.5e, open the General tab and select Type: VHDL 2008.

g)  We can now run the simulation. Under SIMULATION, select Run Simulation > Run Behavioral Simulation, which leads to figure A.7a. Move clk to the top if not there yet.

Figure A.7

Note:   To break a simulation, select Run > Break.

h)  Make the following adjustments:

- Change the time to 320 ns at .

- Click the Restart icon .

- Click the Run for time T icon .

- Click the Zoom Fit icon .

- Select all signals except clk of figure A.7a, click the right mouse button, and change the radix to Radix > Unsigned Decimal.

The final result is shown in figure A.7b.

i)  Finally, inspect the simulation results of figure A.7b and confirm that they comply with figure A.1c.

j)  Click and then for the simulation to advance another 320 ns.

k)  To end a simulation, close the wave pane or type close_sim –force in the Tcl console.

5.   Doing Behavioral Simulation with Tcl Script

This section shows how to run a simulation using tool command language (Tcl, pronounced “tickle”) scripts.

Note:   If you prefer, you can remove the testbench file from the project by right-clicking on the file name and selecting Remove File from Project.

a)  Review the Notes at the beginning of part 4 of this appendix.

b)  The first step is to prepare the Tcl script (check appendix A1, Some Important Tcl Commands for Vivado, at the end of this tutorial). The script of figure A.1e will be used here. Assuming again that the clock period in figure A.1c is 80 ns, the total running time is 320 ns.

c)  You can type the script in the Tcl console (Window > Tcl Console) one line at a time, or you can save it in a text file and run it all at once. For the former, proceed in (d); for the latter, jump to (g).

d)  Under SIMULATION, select Run Simulation > Run Behavioral Simulation. This opens the waveforms pane of figure A.7a. Move clk to the top if not there yet.

e)  Enter the Tcl commands. After entering run 320 (and clicking , if necessary), the screen of figure A.7b will be displayed.

f)  Now that the simulation is done, play with the simulator by doing parts (h)–(k) of this appendix’s part 4.

g)  The Tcl script of figure A.1e can be typed in a text editor and saved (call it registered_adder.tcl) in the same folder the testbench file was saved before (i.e., registered_adder.srcs/sim_1/new). Another option is to use Vivado’s editor as follows.

h)  Under PROJECT MANAGER, click Add Sources, which leads to figure A.5a. Mark Add or create simulation sources and click Next.

i)  In the next screen, click Create File.

j)  In figure A.5b, select Memory File and enter the file name (registered_adder.tcl), then click OK.

k)  Note in figure A.5d that this new file name appears under Memory File in the Simulation Sources list. Click on it; this opens the editor. Type the script, and save the file. Finally, in figure A.5e, change Memory File to TCL.

l)  Run the Tcl file by selecting Tools > Run Tcl Script and pointing to the Tcl file. The result is that of figure A.7b.

m)  Now that the simulation is done, play with the simulator by doing parts (h)–(k) of part 4 of this appendix.

6.   Synthesizing the Design

a)  Under SYNTHESIS, click Run Synthesis.

b)  When finished, the window of figure A.8a opens. Cancel Implementation for now.

Figure A.8

c)  Under SYNTHESIS, click on Open Synthesized Design, then open Schematic, which shows the circuit after synthesis (figure A.8b), which is the circuit that will actually be fitted and routed in the final (Implementation) phase.

d)  Select Window > Design Runs or open the Design Runs tab in the lower part of the main window to see the resources usage. As shown in figure A.8c, three lookup tables (LUTs) and four flip-flops were employed to build this circuit.

7.   Implementing the Design

a)  Under IMPLEMENTATION, click Run Implementation.

b)  When finished, observe that information similar to that in figures A.8b and A.8c is given here.

c)  Observe also in the Project Summary window (click if it is not open) that now Synthesis Status = Complete and Implementation Status = Complete. The resources usage is also summarized at the bottom of the screen.

8.   Doing Functional Simulation with Testbench

Follow this appendix’s part 4, except for part 4(g), in which you must select one of the following:

Simulation > Run Simulation > Run Post-Synthesis Functional Simulation or

Simulation > Run Simulation > Run Post-Implementation Functional Simulation.

9.   Doing Functional Simulation with Tcl Script

Follow part 5, except for part 5(d), in which you must select one of the following:

Simulation > Run Simulation > Run Post-Synthesis Functional Simulation or

Simulation > Run Simulation > Run Post-Implementation Functional Simulation.

10.   Doing Timing Simulation with Testbench

Follow part 4, except for part 4(g), in which you must select one of the following:

Simulation > Run Simulation > Run Post-Synthesis Timing Simulation or

Simulation > Run Simulation > Run Post-Implementation Timing Simulation.

Timing simulation results are shown in figure A.9a. In figure A.9b, a zoomed-in view is shown, so propagation delays can be clearly observed.

Figure A.9

11.   Assigning Pins

a)  Select Flow > Open Implemented Design.

b)  Select Window > I/O Ports or click the I/O Ports tab at the bottom of the screen, which leads to figure A.10.

Figure A.10

c)  In the Package Pin column, enter the names of the pins to which the circuit ports should be routed. Mark them as Fixed.

d)  Update the design by selecting Implementation > Run Implementation (this will include resynthesis).

12.   Programming the FPGA

a)  Select Flow > Open Implemented Design.

b)  Under PROGRAM AND DEBUG, select Generate Bit Stream.

c)  Under PROGRAM AND DEBUG, select Open Hardware Manager to program the FPGA.

d)  Finally, play with the FPGA board to verify whether the implementation works as expected.

Appendix A1. Some Important Tcl Commands for Vivado

Command add_wave: Adds waveforms to the wave pane.

Examples:

add_wave clk Adds wave clk to the wave pane.

add_wave clk -after_wave rst Adds wave clk to the wave pane after the rst wave.

add_wave clk inp outp Adds waves clk, inp, and outp to the wave pane.

add_wave / Adds all ports in the design to the wave pane.

add_wave sum –radix dec Adds wave sum to pane with dec radix.

Note: Allowed radix values are bin (default), unsigned, dec (signed decimal), hex, oct, ascii.

Command add_force: Defines the shape and radix of the waveforms.

Examples:

add_force clk {1 0} {0 40} –repeat_every 80 –cancel_after 2000

Wave clk has value = 1 at time = 0, then 0 at 40 ns, repeats after 80 ns, resulting T = 80 ns, and stops after 2 μs.

add_force clk {1} {0 40} –repeat_every 80 –cancel_after 2000

Same as above (time = 0 does not need to be specified).

add_force inp1 {2} {5 40} –radix unsigned

Wave inp1 is 2 at time = 0, then 5 for time = 40 ns and higher, with unsigned radix (see radix options above).

add_force inp2 {–3} {3 40} {–8 250} –radix dec

Wave inp2 is –3 at time = 0, 3 at 40 ns, then –8 at 250 ns, signed decimal radix.

Command launch_simulation: Opens a simulation.

Command run: Runs a simulation.

Examples:

run 700 ns Runs simulation for 700 ns.

run 700 Same as above (default time unit is ns).

Command close_sim –force. Ends simulation without saving waveforms.

Command restart: Restarts a simulation.

Command current_time: Gets the current time in the simulation.

Command open_project: Opens a project.

Example: open_project c:/xilinx/my_designs/registered_adder.xpr

Command open_report: Displays or copies to an output file the contents of an RPX file.

Examples:

open_report –file results1 design1.rpx Copies the contents of design1.rpx to file results1.

open_report design1.rpx Shows the contents of design1.rpx in the Tcl console.

Tcl script example: See figure A.1e, which contains the stimuli of figure A.1c.

Appendix B: Quartus Prime Tutorial

1.    Introduction

This tutorial is based on Quartus Prime 18.0 Lite Edition (free at intel.com). The circuit used in the tutorial is the registered unsigned adder of figure B.1a, synthesized with the VHDL code of figure B.1b and simulated with the stimuli of figure B.1c. The adder inputs (a, b) are 3-bit signals, while its output (sum) is a 4-bit signal, so overflow never occurs.

Figure B.1

2.    Synthesizing the Design

a)  Start Quartus Prime, which opens the screen of figure B.2a.

Figure B.2

b)  Click New Project Wizard, then Next, which opens the screen of figure B.2b. Enter the project location and name (as shown in the figure) and click Finish, which leads to figure B.3a.

Figure B.3

c)  If this is your first access to Quartus Prime, do the following:

- Set up the default directory for all of your projects at Tools > Options > General. At the bottom of the screen, enter, for example, Default file location: C:/ … /my_designs.

- Set up the local feedback options by going to Tools > Options > Processing and making the selections shown in figure B.3b.

d)  To enter the VHDL code (figure B.1b), open the VHDL editor by clicking on or by selecting File > New and choosing VHDL File as the entry type. After typing the code, save it as registered_adder.vhd.

e)  Define the VHDL version by selecting Assignments > Settings > Compiler Settings > VHDL Input and marking VHDL 2008.

f)  Select the FPGA in Assignments > Device. If the design is not going to be downloaded to any device, choose a simpler FPGA family (for example, Cyclone IV over Cyclone V or 10) because compilation is usually faster (and there is no direct timing simulation support for the latter two). The EP4CE10F17C6 device is employed here.

g)  Compile the code by clicking or by selecting Processing > Start Compilation. When the compilation ends, the Compilation Report of figure B.4 is exhibited. This report contains several pieces of valuable information, some of which are described in the next section.

Figure B.4

Note: You can also run just Analysis and Synthesis () until the syntax has been checked/fixed.

3.    Inspecting Synthesis Results

This part describes some of the results produced by the compiler.

a)  Device type and number of pins: Check in figure B.4 if the device is the intended one (in case one was selected). Check also whether the total number of pins is as expected (3 + 3 + 1 = 7 inputs and 4 + 4 = 8 outputs, totaling 15 pins).

b)  Resources usage: It can be given in number of logic elements (LEs), number of LUTs, number of adaptive logic modules (ALMs), and so on and depends on the FPGA. Note that it is given in “number of LEs” in figure B.4, with 5 used out of > 6k.

c)  Number of registers: This is the number of D-type flip-flops (DFFs) inferred by the compiler. Since sum_reg is a 4-bit signal (figure B.1a), 4 flip-flops are needed, which is indeed what figure B.4 says.

d)  RTL View: This tool shows how the code was understood by the compiler. Select Tools > Netlist Viewers > RTL Viewer, which exhibits the circuit of figure B.5 (or equivalent). Note that it is in perfect agreement with figure B.1a.

Figure B.5

e)  Implemented circuit: The actual implementation can be seen at Tools > Netlist Viewers > Technology Map Viewer (Post-Fitting). The compiler always tries to make simplifications, so in some cases this view is slightly different from the RTL view (but with the same functionality).

f)  Equations: They correspond to the implemented circuit. To see them, select Table of Contents > Fitter > Equations. If the equations are not shown, go to Tools > Options > General > Processing and mark Automatically generate equation files during compilation, then recompile the code. See also this appendix’s part 7 below (Interpreting Fitter Equations).

4.    Simulating the Circuit

This is a manual graphical simulation; we draw the input waveforms, based on what the simulator calculates and how it plots the output waveforms. For an automated simulation (with testbenches or Tcl scripts), use the ModelSim simulator (Appendix C), which is provided with the Quartus Prime software, directly.

Note: Quartus Prime 18.0 does not support direct timing simulation for Cyclone V and 10 FPGAs (the timing simulation shown here is for a Cyclone IV E device).

a)  Click or select File > New. Select then Verification/Debugging Files > University Program VWF and click OK. This opens the wave pane of figure B.6a. Note the default end time of 1 μs.

Figure B.6

b)  Now add signals to the waveform editor. To do so, press the right mouse button in the white area under Name (figure B.6a) and select Insert > Insert Node or Bus, which leads to figure B.7a.

Figure B.7

c)  Click Node Finder, which opens the screen of figure B.7b (only the screen will be empty in your version). In the Filter field, select Pins: all & Registers: post-fitting (or Pins: all if only the circuit ports must be exhibited), then click List. The left column will be filled with all design signals, partially shown in figure B.7b. Click to send all signals to the right or select just the desired signals and click . Click OK twice, which leads to figure B.6b.

d)  Note in figure B.6b that the signals are arranged in alphabetical order. Move clk to the top by clicking on it and holding, then dragging it to the desired position.

e)  Note that the default radix for all signals is binary (denoted by the B letter in the Value column of figure B.6b). Change the radix of all signals except clk to unsigned decimal. To do so, select the signals, click the right mouse button, and select Radix > Unsigned Decimal.

f)  Change the time range by selecting Edit > Set End Time and entering 320 ns, which leads to the wave pane of figure B.6c.

g)  The grid can also be adjusted by selecting Edit > Grid Size. Keep the default value (10 ns).

h)  Now we must draw the input waveforms for clk, a, and b, after which the simulator will compute and draw the output waveforms (sum and sum_reg). The stimuli of figure B.1c are adopted.

- Click on clk to select it. Click then the clock icon and enter 80 ns for the period and 0 for the offset.

- Select the portion of a between 80 ns and 320 ns, then click the arbitrary value icon and enter 5.

- Repeat the process above for b by selecting the respective time intervals and entering 2, 4, and 7.

The result is shown in figure B.6d and is now ready for simulation.

i)  Save the file with the extension .vwf (vector waveform file). You can use the default name (waveform.vwf).

j)  Finally, simulate the circuit by clicking for functional simulation or for timing simulation (note that the latter is the simulation shown in figure B.6e). Equivalently, select Simulation > Run Functional Simulation or Simulation > Run Timing Simulation.

k)  Inspect the results and compare them to those in figure B.1c.

5.    Making Pin Assignments

Notes:

1)  Pin assignments are only allowed if a specific device was selected in part B.2(f).

2)  Pin assignments can be deleted with Assignments > Remove Assignments.

a)  Select Assignments > Pin Planner or click , which opens the window of figure B.8.

Figure B.8

b)  In each line of the Location column, enter the desired pin number (for example, L2 for pin PIN_L2, as shown in the figure for signal a(2)).

c)  When done, recompile the code.

6.    Programming the FPGA

Note: Two files for programming the FPGA can be produced during compilation. That with extension .pof (programmer object file) is stored in an external nonvolatile memory from which the FPGA automatically retrieves the program (for self-programming) at power up. The other, with extension .sof (SRAM object file), programs the FPGA directly, so the configuration is not recovered when the power is turned off. The latter should be used in the experiments.

a)  Connect the FPGA board to a USB port of your computer and turn the board’s power on.

Note: If it is the first time that you are using that board, the USB-Blaster Driver must be installed. This generally occurs automatically when the board is powered up.

b)  Click the Programmer icon or select Tools > Programmer, which opens the window of figure B.9.

Figure B.9

c)  Observe the following in figure B.9: The hardware driver is USB-Blaster; the mode is JTAG; the programmer file is registered_adder.sof (if the file does not show up, click Add File and select it in the output_files subdirectory); and the Program/Configure box is checked.

d)  Click Start, and the device will be programmed. Observe what happens to the board’s LEDs during programming.

7.    Interpreting Fitter Equations

Below are the main symbols used in the Fitter equations.

a)  Logic operators: ! (NOT), & (AND), # (OR), $ (XOR)

b)  Flip-flops:

DFF (D, CLK, CLRN, PRN) (DFF with reset and preset, both active low)

DFFE (D, CLK, CLRN, PRN, ENA) (DFF above plus enable)

DFFEA (D, CLK, CLRN, PRN, ENA, ADATA, ALOAD) (DFF above plus asynchronous data load)

DFFEAS (D, CLK, CLRN, PRN, ENA, ADATA, ALOAD) (DFF above with synchronous clear)

TFFE (T, CLK, CLRN, PRN, ENA) (TFF with reset, preset, and enable)

Note: Recall that in the context of this book a reset signal is called reset when it is asynchronous and it is called clear when it is synchronous.

Appendix C: ModelSim Tutorial

1.    Introduction

This tutorial is based on ModelSim 10.5b, which accompanies Quartus Prime 18.0 Lite Edition (both free at intel.com). The circuit used in the tutorial is the registered unsigned adder of figure C.1a, synthesized with the VHDL code of figure C.1b. The circuit used in the tutorial is the registered unsigned adder of figure C.1a, synthesized with the VHDL code of figure C.1b. The adder inputs (a, b) are 3-bit signals, while its output (sum) is a 4-bit signal, so overflow never occurs. Both functional and timing simulations are shown, using the stimuli of figure C.1c and in the following two situations: using a testbench (figure C.1d) and using a Tcl script (figure C.1e).

Figure C.1

To do functional simulation, two files must be created by the user:

- A design file (registered_adder.vhd, figure C.1b).

- A testbench file (registered_adder_tb.vhd, figure C.1d) or a Tcl file (registered_adder.tcl, figure C.1e).

To do timing simulation, two additional files are needed, both generated by the synthesizer (Quartus Prime, for example):

- A postsynthesis and implementation file (registered_adder.vho), with information about propagation delays.

- A standard delay format (SDF) file (registered_adder_vhd.sdo).

2.    Starting a Project

a)  Create a directory where the project files will be located (C:/ … /registered_adder). If the files of figures C.1b and C.1d were already typed, copy them to this directory.

b)  Launch ModelSim. This leads to figure C.2a. Click Jumpstart, which opens the window of figure C.2b. Click Create a Project, which leads to figure C.2c. Enter the project name (registered_adder) and the project location (C:/ … /registered_adder). Click OK, then in figure C.2d, click Close, which opens the main window (figure C.3, but without the text for the files yet).

Figure C.2

Figure C.3

c)  Adopt the following default setup for the main window:

- In the View menu, mark only Library, Project, and Transcript.

- In the Window menu, mark only Show Toolbar and Show Window Headers.

- In the Window > Toolbars menu, mark Standard, Compile, and Simulate.

The resulting screen is shown in figure C.3, but with the work directory (top left) and the text editor still empty.

d)  Case the files of figures C.1b and C.1d were entered in step (a) above, just open them. Else, select File > New > Source > VHDL, which opens the text editor (at right in figure C.3); type the files and save them as registered_adder.vhd and registered_adder_tb.vhd in the directory created in step (a), keeping them open (in case you need to change/fix them).

e)  Select File > Change Directory and change to the directory created in step (a).

3.    Doing a Functional Simulation with Testbench

Note: To restart a simulation, select Simulate > End Simulation, then follow the steps below again.

a)  Go to Compile > Compile Options and mark Use 1076–2008.

b)  Click or select Compile > Compile, which opens the box of figure C.4a. Select both files and click Compile. When finished, click Done. Note that the work library (figure C.4b) now contains the two files just compiled.

Figure C.4

c)  Expand the work library (as shown in figure C.4b) and double click on the testbench file. Equivalently, select Simulate > Start Simulation (which opens the box of figure C.4c), expand the work library, select registered_adder_tb, and click OK. Both procedures lead to figure 4d.

d)  On the left of figure C.4d, in the sim (simulation) tab, highlight registered_adder_tb, then select Add > To Wave > All items in region, which copies the signal names to the region in the center of figure C.4d (no waveforms yet).

e)  Any signal can be dragged up or down (reset and clock are usually wanted at the top). To do so, press and hold the left mouse button on the signal’s name and move it to the desired position. Move clk to the top, as shown in figure C.4d.

f)  Change the radix of all signals but clk. To do so, select the signals, click the right mouse button and select Radix > Unsigned.

g)  Set the simulation time interval by selecting Simulate > Runtime Options and entering Default Run = 320 ns.

h)  Finally, run the simulation by clicking or by selecting Simulate > Run > Run 100. Click then the Zoom Full icon to see the complete waveforms of figure C.4e.

Note: If it is a functional simulation, note that there are no delays between stimuli changes and respective responses, but there are delays when it is a timing simulation.

i)  Repeat step (h) a few times. Note that the plot advances 320 ns each time.

j)  Clean the waveforms window by clicking the restart icon , then repeat steps (h)–(i). Finally, inspect the results (compare to figure C.1c).

4.    Doing a Timing Simulation with Testbench

a)  Now the propagation delays in the designed circuit must be taken into consideration, so the synthesis/implementation tool must compile the registered_adder.vhd file differently. As an example, the case of Quartus Prime is shown.

- Launch Quartus Prime and build a project using the registered_adder.vhd file.

- Select Assignments > Settings > EDA Tool Settings > Simulation. Select Tool name = ModelSim-Altera and Format = VHDL, then click OK.

- Compile the design. In the work directory, a simulation/modelsim (default name) subdirectory is automatically created, containing the files registered_adder.vho and registered_adder_vhd.sdo.

- Copy these two files to the ModelSim work directory and return to ModelSim.

b)  Compile the files by clicking or selecting Compile > Compile, which leads to figure C.5a. Select the files registered_adder.vho and registered_adder_tb.vhd and click Compile. When finished, click Done.

Figure C.5

c)  Select Simulate > Start Simulation, which leads to figure C.5b.

d)  Expand the work library in figure C.5b and select registered_adder_tb. Open then the SDF tab and click Add, which leads to figure C.5c. In SDF File, browse for the registered_adder_vhd.sdo file. In Apply to Region, type /registered_adder_tb/duv (the “duv” is because this is the instantiation label—see figure C.1d). Click OK (twice).

e)  After this, we are back to figure C.4d, so go to part 3(d) and proceed from there.

The final result is shown in figure C.5d, which has now propagation delays between changes in a or b and the responses in sum and also between positive clock edges and the responses in sum_reg.

5.    Doing Functional Simulation with Tcl Script

Tcl is a scripting language. Like any other EDA software, ModelSim can be run using Tcl commands (this is called command-line mode, as opposed to GUI mode). See some of the main Tcl commands for ModelSim in appendix C1.

a)  The first step is to prepare the Tcl script (check the appendix at the end of this tutorial). The script of figure C.1e will be used here. Assuming that the clock period in figure C.1c is 80 ns, the total running time is 320 ns. Save that script as registered_adder.do in the project directory.

b)  Click or select Compile > Compile, highlight registered_adder.vhd and click Compile. Click Done when finished.

c)  In the Library window, expand the work library and double click registered_adder. Alternatively, you can select Simulate > Start Simulation, which leads to figure C.5b, then expand the work library and select registered_adder.vhd. Click OK, which leads to a screen similar to figure C.4d.

d)  Go now to the Transcript area and type VSIM> do registered_adder.do to obtain the waveforms.

e)  The rest is similar to what was seen in this appendix’s part 3, so continue now beginning with step (e) of that section. You can also type run 320 ns or restart at the VSIM prompt to advance or restart the simulation.

6.    Doing Timing Simulation with Tcl Script

a)  Obtain the additional files needed for timing simulation by following the procedure explained in part 4(a).

b)  Click or select Compile > Compile, highlight registered_adder.vho and click Compile. Click Done when finished.

c)  Select Simulate > Start Simulation. In the opened box (same as figure C.5b), expand the work library (in the Design tab) and select registered_adder.vho. Open then the SDF tab and click Add. In the SDF File field, browse for the registered_adder_vhd.sdo file. Leave the Apply to Region field blank and click OK (twice).

d)  Go now to the Transcript window and type VSIM> do registered_adder.do to obtain the waveforms.

e)  The rest is similar to what was seen in part 3, so continue with part 3(e). You can also use run 320 ns and restart at the VSIM prompt to advance or restart the simulation.

7.    Using Breakpoints

We assume that a testbench is being used (as in parts 3 or 4).

a)  Run the simulation (without breakpoints yet).

b)  The lines of the testbench file that are enumerated in red accept breakpoints. Click on the line number to include a breakpoint (red dot).

c)  Run the simulation with breakpoints included.

d)  To disable a breakpoint, click on it (black dot). To remove it, use the right mouse button.

Appendix C1. Some Important Tcl Commands for ModelSim

Command add wave: Adds a new wave to the wave pane.

Example: add wave clk inp ena outp Adds waves clk, inp, ena, and outp to the wave pane.

Command force for periodic waveforms (like clock): Below, -r stands for “repeat”; freeze is the default value, so it can be omitted. In the example, ‘0’ is the initial value and 150 ns is the period, with a 50 percent duty cycle.

Syntax: force [–freeze] <signal_name> <value> <time>, <value> <time> -r <time>

Example: force clk 0 0 ns, 1 75 ns –r 150 ns

Command force for nonperiodic waveforms (like reset): In the example below, the initial value is ‘1’ during 50 ns, then ‘0’ forever.

Syntax: force <signal_name> <value> run <time> … force <signal_name> <value>

Example: force rst 1 run 50 ns force rst 0

Command run: The first example below causes the simulation to run until the time limit set in the simulator is reached. The other two cause the simulation to advance 300 ns (ps is the default time unit).

Examples: run, run 300 ns, run 300000

Command restart: Causes the waveforms to be cleared, with the simulator returning to time zero.

DO file: A Tcl script is saved in a file with the extension .do. Notice that DO files are a very effective way of documenting your tests.

Command do: Runs a DO file.
Example: do mydofile.do
    Tcl script example: See figure C.1e, which contains the stimuli of figure C.1c.

Appendix D: Simulation Analysis and Recommendations

1.    Simulation Recommendations

a)  In section 5.3 (figure 5.3), see a comparison between functional simulation and timing simulation.

b)  For small designs (fast simulations), use timing simulation directly. Unless stated otherwise, use only timing simulations in this book’s exercises.

c)  Because the propagation delays in FPGA-based designs for low-end to mid-range devices are often (roughly) in the 3ns to 15ns range, adopt clock periods in the 40ns to 80ns range. For combinational units, consider also this range for the distance between stimuli transitions.

d)  The reason for the values suggested above is explained in figure D.1. In (a), the clock period is 20ns; since the response takes about 6ns to settle, it is already too close to the falling clock edge. In (b), the period is 80ns; since the response obviously still takes 6ns, it is still easy to see that it is a timing simulation but without the inconvenience mentioned above.

Figure D.1

e)  The hardest part in the simulation is to define the stimuli correctly and to produce them subsequently (using test benches, for example, as in chapter 18). Unless stated otherwise, a single graph should be used in the exercise solutions, so make sure to choose meaningful stimuli. End values and middle values are particularly important.

2.    Glitches in Combinational Circuits (Single-Bit Output)

Remember what was shown in section 2.4 (figure 2.8a): Glitches are normal in combinational circuits if two or more inputs are allowed to change at the same time. This means that, if a simulation shows a glitch, the circuit is indeed subject to glitches; however, if glitches do not appear, it does not guarantee that the circuit will not be subject to glitches (you might have been lucky in the routing, getting favorable propagation delays, for example).

3.    Glitches in Multi-Bit Signals

A common confusion in simulation is to look at a multi-bit signal and, upon noticing glitches, to believe that glitches indeed exist. A classical example is presented in figure D.2, which shows the output of a 4-bit counter (running from 0 to 15), which shows apparent glitches, particularly in the 3-to-4 and 7-to-8 transitions. However, when we inspect each signal individually, we observe that they are all clean.

Figure D.2

These apparent glitches result because the signals do not change at exactly the same time. For example, in the 7-to-8 transition, all four bits change ("0111" -> "1000"), so the occurrence of momentary intermediate values is inevitable. In summary, glitch analysis must be done one wire (one bit) at a time.

Appendix E: Using Seven-Segment Displays with VHDL

A very helpful resource while teaching VHDL is the set of seven-segment displays (SSDs), available in any FPGA board. SSDs are just 7-LED arrangements (8 if a decimal point is also included), as illustrated in figure E.1a, which includes the segment names (abcdefg). In figure E.1b, the common-anode configuration is presented; note that a '0' lights a segment, while a '1' turns it off (for a common-cathode SSD, just do the opposite). Finally, figure E.1c shows which segments must be lit to obtain the 0-to-F characters (for common-anode).

Figure E.1
(a) SSD with segment names; (b) Common-anode configuration; (c) Corresponding truth table.

To use an SSD, an interface (driver) between the design output and the display input is needed, as illustrated in figures E.2a and b. In figure E.2a, the circuit output (int) is an integer usually ranging from 0 to 9 or from 0 to 15 (so with 4 bits), while in figure E.2b the output (slv) is of type std_logic_vector (or some other bit array type), again with 4 bits. The display interface output (ssd) is obviously always a 7-bit vector (or 8 if a decimal point is used). In the codes below, it is assumed that the SSD is a common-anode display (figures E.1b and c).

Figure E.2
Display interface having (a) an integer (int) or (b) a standard logic vector (slv) as input.

Option 1: With concurrent code (select statement)

In this case, the display interface is constructed in the code proper (architecture’s statement part). Both options (int-to-ssd and slv-to-ssd) are shown below, where the output is assumed to be of type SLV in both.

Option 2 (preferred): With sequential code (case statement in a function)

The use of a function (chapter 14) is recommended in order to keep the code clean. Among other places, a function can be constructed in the architecture’s declarative region. The complete constructions for int_to_ssd and slv_to_ssd are shown below, with the output assumed to be of type SLV in both.

Appendix F: Serial Peripheral Interface

Serial peripheral interface (SPI) is a synchronous serial bus for communication between integrated circuits (installed next to each other, normally on the same board). Developed by Motorola in the 1980s, it is now in widespread use. SPI has some advantages over I²C (see appendix G), like full-duplex communication, higher speed (up to around 100 MHz versus up to 5 MHz), no open-drain pins, and simpler implementation. On the other hand, it requires more wires than I²C and does not have flow control (acknowledgement bits); the data format is less regular; and it only allows the use of one master.

1.    SPI Bus Structure

The SPI bus general structure is depicted in figure F.1. In the single-slave case (figure F.1a), the master (which is often a microcontroller) communicates with the slave through four wires, called SCLK (serial clock, always generated by the master), MOSI (master out slave in), MISO (master in slave out), and SSn (slave select, active low). When SSn is low, the master and slave can communicate using the MOSI/MISO wires. In the multislave case (figure F.1b), multiple SSn wires are employed. The integrated circuit families with SPI support are the same as for I²C, which include microcontrollers, EEPROM and flash memories, A/D and D/A converters, real-time clock circuits, temperature sensors, accelerometers, and so on. SPI is simpler to implement than I²C mainly because there is no bidirectional line, no flow control communication (ack and no-ack bits), and no need for transmitting the slave addresses (slaves are selected by SSn wires).

Figure F.1
SPI bus with (a) a single slave or (b) multiple slaves.

2.    SPI Bus Operation

There are four SPI operating modes determined by the clock phase (CPHA) and clock polarity (CPOL). They are called mode 0 (CPHA = 0,CPOL = 0), mode 1 (CPHA = 0,CPOL = 1), mode 2 (CPHA = 1,CPOL = 0), and mode 3 (CPHA = 1,CPOL = 1). The most popular modes are 0 and 3, illustrated in figure F.2, where dashed lines indicate that values there are irrelevant. In mode 0, SSn is lowered and raised with SCLK low, whereas the opposite occurs in mode 3.

Figure F.2
SPI communication principle.

The gray areas in figures F.2a and b correspond to the start (chip select) and stop (chip deselect) procedures. For example, for T₁ (setup time between the device selection and the first clock edge), the time values in the figures are TSCLK/2 and TSCLK, respectively; though this is fine for most devices, it is necessary to always check the actual value in the device’s datasheet. Analogous conditions are shown for T₂ and T₃, which too must be checked in the device specifications; the value of T₃, in particular, can be larger than TSCLK. As practical examples, the FM25L512 memory chip exhibits, in mode 0, T₁ = T₂ = 10 ns, T₃ = 60 ns, and TSCLK ≥ 50 ns, while the MAX31722 digital thermometer chip exhibits, in mode 3, T₁ = T₃ = 400 ns, T₂ = 100 ns, and TSCLK ≥ 200 ns.

Figure F.2c shows another important information: the master and the slave send information out on negative clock edges and store incoming information on positive clock edges. Consequently, the circuit used to implement the SPI interface must be triggered at negative clock transitions.

It is important to stress that, even though the overall communication procedure (see figure F.2) is standard, the transmitted contents are not. Specific communication rules, which can vary immensely from one device to another, are stored in dedicated registers. For example, the SPI in the Maxim DS1306 RTC has two registers (for status and control), while the SPI in the Ramtron FM25L512 FRAM memory contains a single register (for status). The use of registers is equally common in I²C.

3.    Example: SPI Interface for a FRAM (Ferroelectric RAM) Chip

To illustrate the use of SPI, the FRAM-type EEPROM mentioned above (FM25L512) is used as an example. (A major feature of FRAM technology is that data can be written at high speed—20 MHz in the present device.) It is a 64k bytes nonvolatile memory with SPI access. Its pinout and a setup for the experiments are shown in figure F.3. Notice in figure F.3a that besides the SPI pins (SCLK, SSn, MOSI, MISO), the chip has two other control pins called WPn (write protect) and HOLDn (to handle interrupts), both wired to VDD in the experiments.

Figure F.3
(a) FM25L512 chip pins and (b) a setup for the experiments.

Figure F.4 shows this device’s opcodes and status register. The purpose of the WPn pin, together with bits WPEN, BP1, and BP0, is to allow several protection modes against writing to the memory or status register. Another programmable bit is WEL (write enable latch), which determines whether writing is allowed or not. Because this bit is zeroed at power up or at the upward transition of SSn after a WRITE, WRSR, or WRDI opcode, any write action must start with the WREN opcode because that is the only way of setting WEL to 1 (writing to the status register does not affect this bit).

Figure F.4
(a) Device opcodes and (b) status register; (c) Write and (d) read procedures.

Figures F.4c and F.4d summarize the write and read procedures, respectively (the number of clock cycles in each window is not the actual number). Note that the operation is in mode 0 (as in figure F.2a). Observe also the deselect operation in figure F.4c, which is necessary because this device requires deselection between opcodes.

The circuit discussed in this example is finalized and implemented with VHDL in sections 17.1 and 17.2.

Appendix G: I2C (Inter Integrated Circuits) Interface

I²C is a synchronous 8-bit oriented serial communication bus between integrated circuits installed next to each other (normally on the same board). Created by Philips in the 1980s, it is a two-wire bus with standardized speeds ranging from 100 kbps up to 5 Mbps. I²C has some advantages over SPI (appendix F), such as the use of less wires, flow control (acknowledgement bits), a more formal construction, and the possibility of having more than one master. On the other hand, it is slower, requires pull-up resistors (for open-drain pins), and is more complex to implement.

1.    I2C Bus Structure

The general structure of an I²C bus is depicted figure G.1. Its two wires are called SCL (serial clock) and SDA (serial data), which interconnect one or more master units to a number of slave units. A common ground wire (not shown) is obviously also needed for the system to function. The IC families with I²C support are the same as for SPI (appendix F).

Figure G.1
General I²C bus structure.

As indicated in figure G.1, the clock (SCL), always generated by the master (usually a microcontroller), is unidirectional, whereas the data (SDA) wire is bidirectional. Because SCL and SDA are open-drain lines (the 5Mbps version allows push-pull logic), external pull-up resistors (RPU) must be connected between these wires and VDD. The number of devices sharing the same bus can be up to 128 (7-bit address) or 1024 (10-bit address). Advanced features of the I²C protocol include bus arbitration, clock stretching, general call, reset by software, and so on.

Common values for VDD are 3.3 V and 5 V, but devices with lower voltages, like 1.8 V, are also available. The value of RPU is typically in the 1 kΩ to 33 kΩ range, depending on the total SCL or SDA wire capacitance; if it is large (a long bus with many slaves), then the resistor must be small to achieve the minimum rise-time defined in the I²C specifications.

2.    I2C Bus Operation

Data transfers are done in groups of 8 bits, after which an acknowledgment bit is issued by the receiving end. The general principle is depicted in figure G.2a, which shows a data transmission from master to slave. The start sequence consists of lowering SDA with SCL high (gray area), and the stop sequence (the other gray area) consists of raising SDA with SCL high. This means that during data transmission SDA must remain stable while SCL is high; otherwise, start/stop commands might occur.

Figure G.2
I²C communication principle.

While the master is transmitting (always the most significant bit [MSB] first), the slave remains in high-impedance mode ('Z'), so the master has control over the SDA wire. After the eighth bit is sent, the master goes to the 'Z' state, so the slave can transmit its acknowledgement bit (= 0). In reading procedures, it is the master who emits the ack bit (= 0) after each byte received; however, if the reading includes multiple bytes, then the master sends a no-ack bit (= 1) after the last byte that it wants to retrieve. As depicted in figure G.2b, data is always placed on the SDA wire (by either end) at falling clock edges and is read from it at rising clock edges (thus exactly as in SPI).

It is obviously necessary to respect the minimum values of the time parameters T₁ to T₃ and TSCL as shown in figure G.2a (these are just the main time parameters). T₁ to T₃ are usually smaller than TSCL/2, so TSCL/2 or TSCL can be employed for those time windows (see figure G.2a). As actual examples, the values of these parameters in the Maxim MAX11647 A/D converter device are T₁ = T₂ = 0.6 μs, T₃ = 1.3 μs, and TSCL = 2.5 μs; and in the NXP PCF8593 real-time clock device they are T₁ = T₃ = 4.7 μs, T₂ = 4 μs, and TSCL = 10 μs.

3.    Example: I2C Interface for an A/D Converter

To illustrate the use of I²C, the MAX11647 device (figure G.3a) from Maxim is used as an example. It is a two-channel, 10-bit analog-to-digital converter (ADC), with I²C access at 400 kHz or 1.7 MHz. It operates with VDD = 3.3 V. The reference voltage can be internal (VDD or 2.048 V) or external (between 1 V and VDD). Its address (set at manufacturing) is 0110110.

Figure G.3
(a) MAX11647 pins; (b) Setup for the experiments; (c) Setup register; (d) Configuration register.

A setup for the experiments is shown in figure G.3b. However, before using (reading from) this device, its two registers, called setup and configuration registers (figures G.3c and d), must be programmed. The following will be adopted in the experiments developed later with VHDL:

Figure G.4
I²C write and read procedures for the MAX11647 ADC.

Setup register: MSB = 1 (it is a setup byte), then 101 (reference voltage is 2.048 V, internal, always on), followed by 0 (conversion clock is internal), 0 (unipolar), 1 (internal reset disabled), and finally LSB = − (don’t care). In summary: 1101001−.

Configuration register: MSB = 0 (it is a configuration byte), then 11 (use input selected by CS0), followed by − − − (don’t care), then 0 (use input 0), and finally LSB = 1 (single ended). In summary: 011 − − − 01.

Figure G.4a shows how the registers can be programmed. Communication starts when SDA is lowered with SCL high. The first byte consists of the slave address plus a write (= 0) bit, after which the master goes to the high impedance state ('Z'), so the slave can transmit its acknowledgment bit (= 0). Next, the master sends the setup byte, to which the slave again responds with an ack bit. Finally, the configuration byte is transmitted, so after the ack bit the master ends the write procedure.

The read procedure is depicted in figure G.4b. The master again starts the communication by lowering SDA with SCL high. Next, it transmits the slave address plus a read (= 1) bit, to which the slave responds with an ack bit. After this, the slave takes over the SDA wire (gray shade). First, a time large enough (clock stretch) is given to the slave to perform the analog-to-digital conversion; the device specifications say that tCONV = 6.8 μs, so three clock periods suffice if the speed is 400 kHz (four periods were employed in the experiments). Next, the two-byte result is transmitted to the master (the six MSBs of byte 1 are zero; recall that this is a 10-bit ADC). If the master wants a single reading, it sends a no-ack bit (= 1) after byte 0 is received, so the slave stops sending data and the master ends the communication.

The values of T₁, T₂, T₃, and TCONV are listed in figure G.4. The reader is invited to check whether they are satisfied by the waveforms presented there.

The circuit discussed in this example is finalized and implemented with VHDL in sections 17.3 and 17.4.

Appendix H: Alphanumeric LCD

1.    Alphanumeric LCD

A popular alphanumeric liquid crystal display (LCD) is shown in figure H.1 (see details in the figure caption). Though this kind of display can be found with I²C and other serial interfaces, for low-cost applications the use of parallel access through an HD44780U microcontroller (from Hitachi) or similar is a common choice.

Figure H.1
(a) 16 × 2 alphanumeric LCD; (b) Pinout (with HD44780U microcontroller); (c) Internal layout (16 × 2 8 × 5-dot blocks); (d) Standard 8 × 5-dot exhibition mode (two lines of 7 × 5 characters); (e) Standard 11 × 5-dot configuration (single line of 10 × 5 characters).

2.    Display Pins

The purposes of the signals listed in figure H.1b are as follows.

E (enable, pin 6): Microcontroller’s clock. Writing to the LCD controller occurs at the negative edge of E, while reading occurs at the positive edge.

RS (register select, pin 4): RS = 0 selects the controller’s instruction register (for initialization, for example), while RS = 1 selects its data register (for the characters to be displayed by the LCD).

R/W (read/write, pin 5): 1 for reading, 0 for writing. If R/W = 0, the next negative edge of E causes the present instruction or data to be written to the controller’s register selected by RS. If R/W = 1, data is read from the controller’s register at the next positive edge of E.

DB (data bus, pins 7–14): Bidirectional 8-bit bus for sending/receiving data or instructions to/from the LCD controller.

BF (busy flag, pin 14): The microcontroller sets DB7 to 1 when it is busy, so writing is not allowed. In practice, the use of this signal can be avoided by adopting for each instruction a time long enough to guarantee completion.

3.    Instruction Set

The controller’s instruction set is shown in figure H.2, along with explanatory comments and worst-case execution times. As mentioned above, a common design practice is to adopt a slow clock to operate the LCD, such that any of the instructions has enough time to be completed (so reading BF is not necessary); for example, 500 Hz, which allows 2 ms for execution.

Figure H.2
Instruction set of the LCD controller (HD44780U or equivalent).

The maximum execution times in figure H.2 are for the controller’s internal oscillator operating at 270 kHz. This frequency is set by an external resistor between 75 kΩ (for VDD = 3V) and 91 kΩ (for VDD = 5V). If a different frequency is employed (with different resistor values, the range that can be covered is roughly 100 kHz to 500 kHz), then the execution times must be multiplied by 270kHz/foscillator.

4.    Character Generator ROM

A total of 192 predefined characters of size 7 × 5 (figure H.3) are available in the LCD controller’s character generator ROM (CGROM). The CGROM also contains 32 characters of size 10 × 5. When the former are used (8 × 5-dot blocks), the LCD can operate with two lines; when the latter are used (11 × 5-dot blocks), only single-line operation is possible. User-defined characters are also allowed, so other exhibition modes are possible, such as full-height (16 × 5-dot) characters.

Figure H.3
Predefined characters of size 7 × 5 available in the LCD controller’s ROM.

5.    Microcontroller Initialization by Instructions

The controller’s initialization can be done in two ways: automatically (at power up) or by instructions. The latter (recommended) is used when the power supply conditions for automatic initialization are not met or for safety. It consists of the following (always with R/W = 0 and RS=0; as usual, “−” means “don’t care”):

1)  Turn the power on.

2)  Wait > 15 ms after VDD rises to 4.5 V.

3)  Execute instruction “Function set” with DB = 0011 − − − −.

4)  Wait > 4.2 ms.

5)  Execute instruction “Function set” with DB = 0011 N F − − (choose N and F).

6)  Execute instruction “Display on/off control” with DB = 00001000.

7)  Execute instruction “Clear display” with DB = 00000001.

8)  Execute instruction “Entry mode set” with DB = 000001 I/D S (choose I/D and S).

(Some equivalent microcontrollers have a slightly simpler initialization.)

6.    Typical FSM Structure for Alphanumeric LCD Drivers

A typical FSM for writing to the LCD of figure H.1 is shown in figure H.4, clocked by E at around 500 Hz or 600 Hz. The seven states in the upper box constitute the initialization sequence (compare those states to the sequence described above), while those in the lower box implement the application. Note that RS = 0 in the initialization is needed to write to the instruction register, while RS = 1 is used in the application (except for state ReturnHome), so the writing occurs in the data register. R/W (not shown) is kept permanently low (writing only).

Figure H.4
Typical FSM for writing characters to the alphanumeric LCD of figure H.1.

In the initialization sequence, the first three repetitions of FunctionSet are just to fulfill the time requirement of >4.2 ms, while in the fourth repetition the values of N and F must be specified (N = 1, for two-line operation, and F = 0, for 5 × 8-dot characters, were entered; it is OK to enter the same vector in all repetitions). In the EntryMode state, the selected values were I/D = 1 (DDRAM address incremented automatically) and S = 0 (display not shifted).

The example shown in the application box uses a timed FSM to write eight characters (t = 0 to 7) to the LCD, returning then to the initial display address and overwriting those eight positions. Notice that the overall FSM of figure H.4 is simple enough to be implemented using the pointer-based approach seen in section 3.15 (exercise 17.9).

A complete implementation example is presented in example 17.5.

Appendix I: VGA Video Interface

1.    VGA Interface

The original video graphics array (VGA) interface was introduced by IBM in 1987 for connecting computers to analog cathode ray tube (CRT) video monitors. Many other versions were introduced subsequently (figure I.1b), which were collectively called “VGA modes.” Traditional monitors were replaced with liquid crystal display (LCD) monitors (figure I.1a), which allow for a fully digital operation (the interface is then called digital visual interface or DVI as described in appendix J, instead of VGA). However, most still support VGA interfacing, which is much simpler than DVI, so it is a good place to start when one wants to learn about video drivers.

Figure I.1
(a) VGA (analog video) or DVI (digital video) cable; (b) Some members of the VGA family; (c) Pixel count.

A major specification of a video monitor is its number of pixels, counted as indicated in figure I.1c. As shown in figure I.1b, the resolution of the original VGA standard is 640 × 480, while 1920 × 1080 (Full HD, listed in appendix J) is currently the most common option.

2.    General VGA Interface Architecture

Figure I.2 depicts the general architecture of VGA systems, with the graphics controller (computer side) divided in three parts, as follows.

Figure I.2
VGA interface architecture.

Image generator: Produces the pixel signals (R, G, B), which are converted to analog voltages between 0V and 0.7V by the DACs (usually with a resolution between 6 and 10 bits) before being sent to the monitor. These signals obviously vary from one application to another.

Control signals generator: Produces the VGA clock, clk_vga, plus the actual control signals of Hactive (horizontal active window), Vactive (vertical active window), dena (display enable), Hsync (horizontal sync), and Vsync (vertical sync). This block is application-independent (it depends only on the VGA mode), so its design is always the same.

DDC (Display Data Channel): Allows the computer to read the display’s features (supported resolutions, timings etc.), stored in a ROM with extended display identification data (EDID) format. The original VGA mode (640 × 480 × 60Hz) is supported by any monitor by default. Notice that this communication employs the I²C protocol (appendix G). This block too is application-independent.

Hsync and Vsync are responsible for determining when a new line or a new frame should start, respectively, with their timings defining the VGA mode. Hactive and Vactive represent the time intervals during which an image is actually being drawn on the screen. Finally, dena is responsible for turning the pixel signals off during retrace, so it can be obtained by simply ANDing Hactive and Vactive. Note that only two of the five control signals are transmitted to the monitor.

3.    VGA Connector

Figure I.3 shows a VGA connector and its pins. There are five main signals, sent out through pins 1–3 and 13–14. Wires 1 to 3 transmit the color signals (R, G, B) to the monitor, which are analog voltages between 0V and 0.7V on two parallel 75Ω resistors (all other signals are digital), while wires 13–14 transmit the horizontal and vertical synchronism signals (Hsync, Vsync). The reader is invited to compare these pins to the wires in figure I.2.

Figure I.3
VGA connector.

4.    Construction of Control Signals

Figure I.4 shows the specifications for several VGA modes. It shows also the waveforms for Hsync and Hactive (based on clk_vga), which consist of four parts (all measured in number of pixels; i.e., number of clock cycles), called H_LOW (width of the horizontal synchronization pulse), HBP (horizontal back porch), H_HIGH (active line display interval), and HFP (horizontal front porch).

Figure I.4
Examples of VGA modes and corresponding horizontal time parameters.

The vertical time diagram is depicted in figure I.5, also consisting of four parts (all measured in number of lines; i.e., number of Hsync cycles), called V_LOW (width of the vertical synchronization pulse), VBP (vertical back porch), V_HIGH (active column display interval), and VFP (vertical front porch).

Figure I.5
Examples of VGA modes and corresponding vertical time parameters.

For example, in the 640 × 480 × 60Hz VGA option, the drawing of one line takes 800 cycles of clk_vga (figure I.4), while one frame requires a time equivalent to 525 lines (figure I.5). Consequently, to be able to generate 60 frames per second, the frequency of clk_vga must be 800 ● 525 ● 60 ≈ 25MHz (the original value is 25.175MHz, for a refresh rate of 59.94Hz, inherited from the National Television System Committee [NTSC] television system).

A complete implementation example is presented in example 17.6.

Appendix J: DVI Video Interface

1.    DVI Interface

Digital visual interface (DVI) is a digital video interface standard developed by the Digital Display Working Group (DDWG) in 1999 for connecting uncompressed video from computers to LCD monitors (figure J.1a). Its core consists of transition minimized differential signaling (TMDS) links (appendix K).

Figure J.1
(a) DVI cable between computer and LCD monitor; (b) Examples of monitor resolutions.

As usual, a major specification of a video monitor is its number of pixels. Figure J.1b lists popular standards, of which full high definition (Full HD) is currently the most common option.

2.    General DVI Interface Architecture

Figure J.2 shows the DVI interface circuit at the two ends of the DVI cable (the control generator and the image generator will be introduced later), which consists of three parts. The first block is the TMDS transmitter or receiver. The second block is the display data channel (DDC), through which the computer reads the display’s features (supported resolutions, timings, and so on), stored in a ROM with EDID format; notice that this communication employs the I²C protocol (appendix G). The third block, which is optional, is a generator of VGA signals (appendix I) to maintain compatibility with analog VGA monitors.

Figure J.2
DVI circuit at the two ends of the DVI cable. A complete DVI connector (figure J.3) has 29 pins, corresponding to the 25 wires above plus 4 grounded shields.

The TMDS transceiver can contain one or two links, each consisting of three TMDS channels (see white box at the top of figure J.2), which transmit alternately pixel data (R, G, and B colors, with 8 bits each) or control data (2 bits per channel, with Hsync and Vsync in the first channel and up to five pairs of reserved control bits, C0-C1, …, C8-C9, in the other channels). Pixel data is transmitted when dena (display enable) is high.

The clock of DVI systems can go from 22.5 MHz (25 MHz is used for basic 640 × 480 × 60Hz VGA mode) up to 165 MHz (maximum resolution). When a pixel rate above 165 Mpps is needed (with blanking included), dual link must be used, but both links must operate at the same speed.

The bit rate is independent from the type of data (pixel or control) being transmitted because in either case 10 bits are produced by the TMDS encoder (appendix K). Consequently, when operating at full speed (165 Mpps for single link or 330 Mpps for dual link), a bit rate of 1.65 Gbps is needed through the wires of the DVI cable. To operate at such high frequency, the frequency of the system clock usually needs to be increased, which is done with PLLs (section 2.8), shown near the center of figure J.2.

3.    TMDS Transmitter

The TMDS link is clearly the most complex part of DVI, so further details are given in appendix K, which is dedicated to it.

4.    DVI Connector

A (complete) DVI connector is shown in figure J.3. The purpose of its pins is summarized below.

Figure J.3
DVI connector (check corresponding signals in figure J.2).

a)  There are three pairs of wires, called tmds0 (pins 18–17, signal B), tmds1 (pins 10–9, signal G), and tmds2 (pins 2–1, signal R), to transmit RGB in link 0.

b)  There are three pairs of wires, called tmds3 (pins 13–12, signal B), tmds4 (pins 5–4, signal G), and tmds5 (pins 21–20, signal R), to transmit RGB in link 1.

c)  There is one pair of wires, called tmds_clk (pins 23–24), for transmitting the DVI clock from the host to the device.

d)  Each of the 7 pairs of wires above has a shielding cover and is connected to pins 3, 11, 19, or 22.

e)  There are also two DDC wires (pins 6 and 7) for I²C communication with the EDID ROM.

f)  There is a pair of wires (pins 14 and 15) that feeds the EDID ROM when the monitor is off.

g)  The connector also shows a hot plug detection (HPD) wire (pin 16), which allows the host to know whether there is a device connected to the DVI cable.

h)  Finally, the remaining six wires (R, G, B, Hsync, Vsync, plus ground for R/G/B) are for compatibility with analog-input monitors.

Compare the pins in figure J.3 to the wires in figure J.2.

A complete DVI interface implementation is presented in example 17.7.

Appendix K: TMDS Link

1.    TMDS Link

Transition minimized differential signaling (TMDS) is a line code for serial transmission of video signals that was introduced in 1999 by Silicon Image. It is used as part of the DVI (Digital Visual Interface, appendix J) video standard for interconnecting desktop computers to LCD monitors, and also as part of the high-definition multimedia interface (HDMI) standard for connecting high-definition videogame consoles, set-top boxes, and so on to high-definition TV (HDTV) monitors and video projectors.

The general architecture of TMDS links was shown in figure J.2 of appendix J. A single of those channels is depicted in figure K.1 below. The transmitter consists of an 8B/10B encoder, a serializer, and a pair of current-mode logic (CML) input and output pins.

Figure K.1
TMDS link with a single channel.

The 8B/10B encoder coverts an 8-bit word into a 10-bit word with fewer internal transitions in general, thereby reducing high-frequency emissions. It also provides near perfect DC balance (number of ones close to the number of zeros) on the communication wires, improving noise margin. Be aware, however, that this 8B/10B code is not the same as the original 8B/10B code introduced by IBM in the 1980s.

As shown in figure J.2 of appendix J, the pixel data is 8 bits wide, while the control data is just 2 bits wide. Since the TMDS encoder must produce 10-bit words from 8-bit words, the regular encoding procedure (described ahead) is applied only to pixel data. For control data, fixed 10-bit words are used. Recall also from figure J.2 of appendix J that the clock (pixel) frequency can be as high as 165 MHz; and so because for each pixel a 10-bit vector must be transmitted serially, the transmission clock must be 1.65 GHz. This higher frequency is produced by a PLL (section 2.8), which multiplies the pixel clock frequency by ten.

2.    TMDS Encoding Algorithm

The 8B/10B encoder of TMDS is illustrated in figure K.2. The first stage is an XOR/XNOR operator and the second stage is an inverter/noninverter operator. The former minimizes the number of transitions (less electromagnetic interference, or EMI), while the latter minimizes the disparity (total number of ones minus total number of zeros transmitted over a certain time period, which should be as close to zero as possible).

Figure K.2
The 8B/10B encoder of TMDS.

The input data, din(7:0), is passed through the XOR or XNOR gate, producing the internal vector x(7:0) (figure K.2). The chosen gate is that that produces the x(7:0) vector with fewer transitions, and a ninth bit, dout(8), is appended to dout to inform the choice made (0 for XNOR, 1 for XOR). Next, x(7:0) is inverted or not, producing dout(7:0), to which a tenth bit, dout(9), is also appended to indicate the choice made (1 when inverted, 0 otherwise). This choice is a function of the accumulated disparity, and it is made to minimize it. Even though there are only 256 possible values for the pixel data, a total of 460 distinct 10-bit words can result because some inputs can produce two distinct codewords, depending on the disparity.

As shown in figure J.2 of appendix J, in the case of video systems using the DVI or HDMI interface (of which TMDS is part), pixel data is transmitted when dena (display enable) is asserted, while control data is transmitted otherwise.

A hardware-friendly version for the TMDS encoding algorithm is presented in figure K.3 (see circuit ports in the inset on the upper left corner), where the following is employed:

Figure K.3
TMDS 8B/10B encoding algorithm.

din(7:0) = Pixel data (8 bits) when dena = 1 or control data (2 bits) when dena = 0

x(7:0) = Internal vector

dout(9:0) = Output word (10 bits)

dena = Display enable

onesD = Number of ones in din(7:0)

onesX = Number of ones in x(7:0)

zerosX = Number of zeros in x(7:0)

disp = Accumulated disparity

Because zerosX + onesX = 8, testing onesX > zerosX is the same as testing onesX > 4. Also, to simplify matters, notice that x has an extra bit in the flowchart, which is passed to dout(8) later.

A series of encoded examples are shown in figure K.4. Most inputs can produce two distinct outputs (depending on the disparity), but some (not shown) can only produce one. Observe that all ten output bits are taken into account when computing the accumulated disparity.

Figure K.4
Examples of TMDS encodings for several input and disparity values.

3.    CML Pins

A CML transmitter/receiver pair is shown in figure K.5a. The TMDS specifications determine that the two output voltages must be VDD (high) and VDD–0.4V (low). Because there are 50Ω resistors at both ends of the wires, and only one transistor in the differential pair is on at a time, the low voltage is (50||50)16mA = 0.4V below VDD, as expected.

Figure K.5
(a) CML circuits; (b) CML transmitter emulated with regular 3.3V LVCMOS pads plus resistors.

CML I/Os are not available in FPGAs. However, they can be emulated with the circuit of figure K.5b, which requires just a pair of external 270Ω resistors (R₂) connected to a pair of regular 3.3V LVCMOS pins. Notice that the voltage drop is [(VDD–VLOW)/(R₁ + R₂)]R₁ ≈ 0.4V, hence fulfilling the specification.

See the implementation in example 17.8 and the complete application in example 17.7.

Appendix L: Using Phase-Locked Loops with VHDL

This tutorial shows how to instantiate PLLs in FPGAs when using Vivado or Quartus Prime. In both cases, the PLL’s default ports are clock_in, clock_out (one or more), reset, and locked (of the last two, at least locked can be disabled). The circuit of figure L.1 will be used as an example (see also complete application in example 17.7).

Figure L.1

I. Instantiating PLLs with Vivado

1)  Start Vivado, create a project, and enter a VHDL code for the circuit of figure L.1, without the PLL yet (i.e., without lines 14–18 and 22).

2)  Under Project Manager, select IP Catalog > FPGA Features and Design > Clocking > Clock Wizard, which opens the window of figure L.2. Pay particular attention to the five arrows:

Figure L.2

- On the left, notice the PLL’s default ports (for clock in, clock out, reset, and locked).

- In the center, mark PLL.

- At the top, enter a name for the component (say, my_pll).

- At the bottom, enter name (clk_50MHz) and frequency (50 MHz) for the input clock.

2)  Open the second tab (Output Clocks) of figure L.2, leading to figure L.3. Note the four arrows:

Figure L.3

- Near the top, enter name (clk_120MHz) and frequency (120 MHz) for the output clock; observe that not any speed is allowed.

- At the bottom, unmark the reset and locked ports.

- On the left, notice that now the only active ports are those for clock in and clock out.

- Click OK.

3)  Returning to your design, observe in the project IP Sources list (figure L.4) that a component with instantiation template called my_pll.vho was created. Click on it, and the code on the right of figure L.4 will be displayed, even having indications on where to cut to get just the right portion of code for component declaration.

4)  Copy this component declaration to your project (lines 14–18 in the code shown above) and make the instantiation of that component (line 22).

Figure L.4

Result: The RTL view produced by Vivado after compilation is shown in figure L.5.

Figure L.5

II. Instantiating PLLs with Quartus Prime

1)  Start Quartus Prime, create a project, and enter a VHDL code for the circuit of figure L.1 but without the PLL yet (i.e., without lines 14–18 and 22).

2)  Access Tools > IP Catalog > Library > Basic Functions > Clocks, PLLs, and Resets > PLL > Altera PLL, which leads to figure L.6.

Figure L.6

- Mark VHDL.

- Enter a name for your component (say, my_pll).

- Click OK, which opens the MegaWizard (figure L.7).

Figure L.7

3)  In figure L.7, pay particular attention to the four arrows:

- On the left, notice the PLL’s default ports (clock in, clock out, reset, and locked).

- Near the top, enter the frequency (50 MHz) for the reference (input) clock.

- In the center, enter the desired frequency (120 MHz). The frequency that will be actually generated is displayed right below it (not all speeds are possible). Choosing Fractional-N PLL instead of Integer-N PLL gives additional options.

- Notice that the locked port can be disabled (as we did for Vivado above).

- Click Finish.

4)  Figure L.8 is exhibited when done. Click Exit, which leads to figure L.9. Click Yes.

Figure L.8

Figure L.9

5)  Go to Project > Add/Remove Files in Project and confirm that the my_pll.qip and my_pll.sip files were added automatically to the design, as shown in figure L.10.

Figure L.10

6)  Open, for editing, the file my_pll.vhd. Copy its entity to your design to be used as a component. If the rst and locked ports were not disabled, lines 14–22 of the previous code would become lines 14–24 below. Note that, in this example, rst and locked were disabled during instantiation (line 24), with the former set to zero and the latter left unconnected.

Figure L.11

Result: The RTL view produced by Quartus Prime is shown in figure L.11.

Appendix M: List of Enumerated Examples and Exercises

Chapter 6. Code Structure and Composition

Examples

Example 6.1. Parity detector

Example 6.2. D-type flip-flop (DFF)

Example 6.3. Registered add-and-compare circuit

Chapter 7. Predefined Data Types

Examples

Example 7.1. Tri-state buffer

Example 7.2. Circuit with “don’t care” values

Exercises

Exercise 7.1. Required packages

Exercise 7.2. Possible data types

Exercise 7.3. Number of bits and decimal values

Exercise 7.4. Legal versus illegal aggregations

Exercise 7.5. Legal versus illegal concatenations

Exercise 7.6. Resizing unsigned and signed data

Exercise 7.7. Resizing signed and standard-logic data

Exercise 7.8. Resizing fixed-point data

Exercise 7.9. Resizing floating-point data

Exercise 7.10. Type conversion (1)

Exercise 7.11. Type conversion (2)

Exercise 7.12. Strength-stripping functions

Exercise 7.13. Type qualification (1)

Exercise 7.14. Type qualification (2)

Chapter 8. User-Defined Data Types

Examples

Example 8.1. Multiplexer with a user-defined input type

Example 8.2. Vector-matrix multiplier with user-defined types

Exercises

Exercise 8.1. Dimensionality of data arrays

Exercise 8.2. User-defined 1D × 1D array types

Exercise 8.3. User-defined 2D array types

Exercise 8.4. User-defined 1D × 1D × 1D array types

Exercise 8.5. Legal versus illegal assignments (1)

Exercise 8.6. Legal versus illegal assignments (2)

Exercise 8.7. Checking whether a data array contains only zeros

Exercise 8.8. Setting all elements of a data array to zero

Exercise 8.9. Checking arrays of integers

Exercise 8.10. Resetting arrays of integers

Exercise 8.11. Checking whether an integer is odd

Chapter 9. Operators and Attributes

Example

Example 9.1. Delay line built with attribute keep

Exercises

Exercise 9.1. Logical operators (1)

Exercise 9.2. Logical operators (2)

Exercise 9.3. Logical operators (3)

Exercise 9.4. Arithmetic operators (1)

Exercise 9.5. Arithmetic operators (2)

Exercise 9.6. Arithmetic operators (3)

Exercise 9.7. Comparison operators (1)

Exercise 9.8. Comparison operators (2)

Exercise 9.9. Comparison operators (3)

Exercise 9.10. Shift and concatenation operators

Exercise 9.11. Attributes of array type and objects

Exercise 9.12. State machine encoding (1)

Exercise 9.13. State machine encoding (2)

Exercise 9.14. Binary-to-BCD conversion

Chapter 10. Concurrent Code

Examples

Example 10.1. Priority encoder

Example 10.2. Conditional adder instantiation

Example 10.3. Carry-ripple adder built with full-adder components

Example 10.4. Hamming-weight calculator

Example 10.5. Signed integer adder

Example 10.6. Floating-point adder and multiplier

Chapter 11. Concurrent Code: Practice

Examples

Example 11.1. Vectors absolute difference calculator

Example 11.2. Programmable combinational delay line (structural)

Example 11.3. Sine calculator with integers and ROM-type memory

Exercises

Part 1: Combinational Logic Circuits

Exercise 11.1. Multiplexer (1)

Exercise 11.2. Multiplexer (2)

Exercise 11.3. Address decoder

Exercise 11.4 Leading-ones counter

Exercise 11.5: Largest cluster of ones

Exercise 11.6. Circuit with “don’t care” outputs

Exercise 11.7. Binary-to-BCD converters

Exercise 11.8. Ring oscillator

Part 2: Combinational Arithmetic Circuits

Exercise 11.9. Unsigned and signed integer multipliers

Exercise 11.10. Unsigned and signed integer dividers

Exercise 11.11. Overflow analysis in division of integers

Exercise 11.12. Division of integers with rounding

Exercise 11.13. Floating-point adder and multiplier

Exercise 11.14. Floating-point subtracter and divider

Exercise 11.15. Generic chain-type adder array

Exercise 11.16. Generic tree-type adder array

Exercise 11.17. Multiple detector

Exercise 11.18. Square-root calculator

Chapter 12. Sequential Code

Examples

Example 12.1. Flip-flop inference with and without sensitivity list

Example 12.2. Consequences of incomplete sensitivity lists

Example 12.3. Counter with is_max flag and RTL analysis

Example 12.4. Counters with signal and variable

Example 12.5. Timer with BCD outputs

Example 12.6. Counter with is_max flag implemented with variable

Example 12.7. Flip-flop with q and qbar outputs

Example 12.8. Registered adder

Example 12.9. Shift register with odd implementations

Example 12.10. Recommended shift register implementation

Example 12.11. Leading-ones counter with combinational loop

Chapter 13. Sequential Code: Practice

Examples

Example 13.1. Generic tree-type adder array

Example 13.2. Single-switch debouncer

Example 13.3. FIR filter with fixed coefficients

Example 13.4. Sequential square-root calculator

Exercises

Part 1: Signal versus Variable

Exercise 13.1. Guess the circuit (1)

Exercise 13.2. Guess the circuit (2)

Exercise 13.3. Guess the circuit (3)

Part 2: Combinational Circuits

Exercise 13.4. Hamming-weight calculator

Exercise 13.5. Largest cluster of ones

Exercise 13.6. Generic chain- and tree-type maximum detector

Part 3: Counters and Clock Dividers

Exercise 13.7. Counter with failure flag

Exercise 13.8. Counter with is_max flag

Exercise 13.9. Direct and reversed gray counters

Exercise 13.10. Clock divider by any integer with symmetric phase

Exercise 13.11. Clock divider by odd integer with positive-edge-only flip-flops

Part 4: Timers and Associated Circuits

Exercise 13.12. Single-switch debouncer with a test circuit

Exercise 13.13. Simplified single-switch debouncer

Exercise 13.14. Multi-input debouncer for non-concurrent switches

Exercise 13.15. Multi-input debouncer for independent switches

Exercise 13.16. Multi-switch debouncer with clock gating

Exercise 13.17. Pulse width modulator (PWM)

Exercise 13.18. Timer (1)

Exercise 13.19. Timer (2)

Exercise 13.20. Kitchen timer (1)

Exercise 13.21. Kitchen timer (2)

Part 5: Synchronism

Exercise 13.22. One-shot and pulse-capture circuits

Exercise 13.23. Pulse stretcher

Exercise 13.24. Frequency meter

Exercise 13.25. Measuring the width of asynchronous pulses

Part 6: Shifters

Exercise 13.26. Tapped delay line

Exercise 13.27. Pseudo-random sequence generator

Exercise 13.28. Continuously shifting display

Part 7: Controllers

Exercise 13.29. Reference-value generator

Exercise 13.30. Air conditioning controller

Exercise 13.31. PWM breathing effect

Exercise 13.32. Quick-finger game

Part 8: Serial Arithmetic Circuits

Exercise 13.33. Serial absolute difference between two vectors calculator

Exercise 13.34. Moving-average calculator

Exercise 13.35. Determining the variance of video frames

Part 9: Filters

Exercise 13.36. FIR filter with underlimited data path

Exercise 13.37. FIR filter with programmable coefficients

Exercise 13.38. IIR filter with transposed direct form I architecture

Part 10: With Component Instantiation (Structural Code)

Exercise 13.39. Modular counter (structural code)

Exercise 13.40. Debouncer with a test circuit (structural code)

Exercise 13.41. Frequency meter (structural code)

Exercise 13.42. Tapped delay line (structural code)

Exercise 13.43. Air conditioning controller (structural code)

Exercise 13.44. PWM breathing effect (structural code)

Chapter 14. Packages and Subprograms

Examples

Example 14.1. Package with function

Example 14.2. Function ceil_log2 in a package

Example 14.3. Function ceil_log2 in an architecture

Example 14.4. Function slv_to_integer in a process

Example 14.5. Function binary_to_bcd

Example 14.6. Procedure find_min_and_max

Exercises

Exercise 14.1. Function binary_to_bcd

Exercise 14.2. Function integer_to_slv

Exercise 14.3. Function integer_to_onehot

Exercise 14.4. Function leading_ones

Exercise 14.5. Function Hamming_weight

Exercise 14.6. Function sort_binary

Exercise 14.7. Function sla for std_ulogic_vector (overloaded operator)

Exercise 14.8. Procedure find_min_and_max

Exercise 14.9. Procedure equalize_lengths

Exercise 14.10. Procedure add_signed

Exercise 14.11. Procedure mean_median

Chapter 15. The Case of State Machines

Exercises

Exercise 15.1. Checking the templates for regular (category 1) machines

Exercise 15.2. Checking the templates for timed (category 2) machines

Exercise 15.3. Checking the templates for recursive (category 3) machines

Chapter 16. The Case of State Machines: Practice

Examples

Example 16.1. ASCII-sequence detector

Example 16.2. Garage door controller

Example 16.3. Password decoder

Example 16.4. SPI interface for an A/D converter

Example 16.5. SRAM memory interface

Example 16.6. Datapath controller for a serial multiplier

Exercises

Part 1: Exercises with Regular FSMs

Exercise 16.1. Checking the templates for regular (category 1) machines

Exercise 16.2. Manchester encoder

Exercise 16.3. Flag monitor

Part 2: Exercises with Timed FSMs

Exercise 16.4. Checking the templates for timed (category 2) machines

Exercise 16.5. Pulse stretcher

Exercise 16.6. Multi-input debouncer for non-concurrent switches

Exercise 16.7. Playing with a seven-segment display

Exercise 16.8. Residential alarm system

Exercise 16.9. Residential alarm system with buzzer

Part 3: Exercises with Recursive FSMs

Exercise 16.10. Checking the templates for recursive (category 3) machines

Exercise 16.11. Reference-value definer with embedded debouncer (a recursive timed FSM)

Exercise 16.12. SRAM interface

Chapter 17. Additional Design Examples

Examples

Example 17.1. SPI interface for an EEPROM device (with FSM)

Example 17.2. SPI interface for an EEPROM device (with pointer)

Example 17.3. I²C interface for an A/D converter (with pointer)

Example 17.4. I²C interface for an A/D converter (with pointer built with FSM)

Example 17.5. Digital watch with liquid crystal display

Example 17.6. VGA video interface for a hardware-generated image

Example 17.7. DVI video interface for a hardware-generated image

Example 17.8. TMDS 8B/10B encoder

Exercises

Part 1: Exercises with SPI Protocol

Exercise 17.1. SPI interface for an A/D converter

Exercise 17.2. SPI interface for a temperature sensor

Exercise 17.3. SPI interface for an accelerometer

Part 2: Exercises with I2C Protocol

Exercise 17.4. I²C interface for an A/D converter

Exercise 17.5. I²C interface for an EEPROM

Exercise 17.6. I²C interface for a temperature sensor

Exercise 17.7. I²C interface for an accelerometer

Exercise 17.8. I²C interface for a real-time clock (RTC)

Part 3: Exercises with Alphanumeric LCD

Exercise 17.9. Digital watch with LCD display (pointer-based implementation)

Exercise 17.10. Blinking “VHDL” word

Exercise 17.11. Display your name on an alphanumeric LCD

Part 4: Exercises with VGA Video Driver

Exercise 17.12. Hardware-generated image with VGA monitor (banner)

Exercise 17.13. Hardware-generated image with VGA monitor (rotating bars)

Exercise 17.14. Digital clock with VGA monitor

Exercise 17.15. Display your picture on a VGA monitor

Part 5: Exercises with DVI Video Driver

Exercise 17.16. Hardware-generated image with DVI monitor (banner)

Exercise 17.17. Hardware-generated image with DVI monitor (rotating bars)

Exercise 17.18. Digital clock with DVI monitor

Exercise 17.19. Display your picture on a DVI monitor

Chapter 18. Introduction to Simulation with Testbenches

Examples

Example 18.1. Stimuli generator

Example 18.2. Binary-to-gray converter (a combinational circuit)

Example 18.3. Registered multiplexer (a sequential circuit)

Example 18.4. Binary-to-gray converter (using data files)

Exercises

Exercise 18.1. Signed adder

Exercise 18.2. Sine calculator

Exercise 18.3. Sequential square-root calculator

Exercise 18.4. Pulse width modulator (PWM)

Bibliography

1. IEEE, 1076–2008—Standard VHDL Language Reference Manual (Piscataway, NJ: IEEE Standards Association, 2009).

2. IEEE, 1076.6–2004—Standard for VHDL Register Transfer Level (RTL) Synthesis (Piscataway, NJ: IEEE Standards Association, 2004.

3. Volnei A. Pedroni, Circuit Design and Simulation with VHDL, 2nd ed. (Cambridge, MA: MIT Press, 2010).

4. Volnei A. Pedroni, Finite State Machines in Hardware: Theory and Design (with VHDL and SystemVerilog) (Cambridge, MA: MIT Press, 2013).

5. Ricardo Jasinski, Effective Coding with VHDL: Principles and Best Practices (Cambridge, MA: MIT Press, 2016).

6. Peter Ashenden and Jim Lewis, VHDL-2008 Just the New Stuff (Amsterdam: Elsevier Morgan Kaufmann, 2008).

Index

& (see Operators, concatenation)

+ (see Operators, arithmetic)

'−' (“don’t care” logic symbol), 163

− (see Operators, arithmetic)

−− (single-line comment), 120

* (see Operators, arithmetic)

** (see Operators, arithmetic)

/ (see Operators, arithmetic)

/* */ (enclosed comment), 120

/= (see Operators, comparison)

:= (variable, constant, and initial/default value assignment symbol), 139–142

?/= (see Operators, comparison)

?< (see Operators, comparison)

?<= (see Operators, comparison)

?= (see Operators, comparison)

?> (see Operators, comparison)

?>= (see Operators, comparison)

< (see Operators, comparison)

<= (signal assignment symbol; see Operators, comparison)

= (see Operators, comparison)

> (see Operators, comparison)

>= (see Operators, comparison)

1D array, 197

1D×1D array, 197–198

1D×1D×1D array, 199

2D array, 198

3D array, 199

abs operator. See Operators, arithmetic

after keyword, 477

Aggregation. See Array manipulation

Alias, 238–239, 318

all keyword. See Process

and operator. See Logical operators

Architecture body, 138

Arithmetic circuits design, 259–264

Arithmetic operators. See Operators, arithmetic

Array manipulation, 173–178

aggregation, 173–175

concatenation, 175, 227

resize function, 175–178

Arrays, 156, 194–199

1D array, 197

1D×1D array, 197–198

1D×1D×1D array, 199

2D array, 198

3D array, 199

addressing complex arrays, 196–199

array type declaration, 156, 195

array types, 194, 195–201

building complex arrays, 196–199

checking and resetting arrays, 199–201

assert statement. See Statements

Assignment mistakes, 201–204

Attributes

of array types and objects, 230–231

‘event attribute, 232, 284

fsm_encoding attribute, 234, 375

fsm_safe_state attribute, 235

keep attribute, 236

of named entities, 231–232

‘quiet attribute, 232

ROM and RAM attributes, 237

safe attribute, 236

of safe state machines, 235–236

of scalar types, 229–230

of signals, 231

’stable attribute, 232, 477

of state machine encoding, 234–235

syn_encoding attribute, 235, 375

of synthesis, 234–237

bit, 160

bit_vector, 160

boolean, 161

boolean_vector, 161

Carry bit, 16–20

case statement. See Statements

Cast. See Type conversion

Chain-type versus tree-type structures, 3–4

character, 162

Choosing good names, 123–127

for architecture bodies, 124

for constants, 124

for entity declarations, 124

for files, 127

for functions and procedures, 125

for signals and variables, 125

for types, 126

Circuits, combinational (review)

adder arrays, 12–13

address decoder, 5

ALU, 15–16

binary-to-BCD converters, 6–10

carry-ripple adder, 11

comparators, 14–15

fast adders, 11–12

full-adder (FA) unit, 10–11

incrementer, decrementer, two’s complementer, 14

logic gates, 2–3

multiplexer, 4–5

parallel multiplier, 14–15

parity detector, 6

priority encoder, 6

subtracters, 13–14

Circuits, combinational (VHDL examples). See list in Appendix M, 571–579 (examples, 6.1, 7.1, 7.2, 8.1, 8.2, 9.1, 10.1 to 10.6, 11.1 to 11.3, 12.11, 13.1, 14.5, 14.6, 18.2, 18.4)

Circuits, combinational (VHDL exercises). See list in appendix M, 571–579 (exercises, 9.14, 11.1 to 11.18, 13.4 to 13.6, 14.1, 14.3 to 14.11, 18.1, 18.2)

Circuits, sequential (review)

binary-to-BCD converter, 46–47

clock dividers, 42–46

clock-domain crossing, 49–53

clock multipliers, 46–48

counters, asynchronous, 38–39

counters, Gray, Johnson, and one-hot, 39–41

counters, synchronous, 37–38

debouncers, 55–58

digital FIR filters, 61–64

digital IIR filters, 64–65

DVI video interface, 555–557

flip-flops, 31–34

frequency meter, 49–52

I²C (inter integrated circuits) interface, 541–544

Latches, 29–30

LCD (liquid crystal display), 545–549

one-shot and pulse-capture circuits, 54–55

PLLs, 46–48

pseudo-random sequence generators, 60–61

PWM (pulse width modulator), 59–60

reference-value generators, 58–59

serializer and deserializer, 65–66

shift registers, 36–37

signal generators, 41

SPI (serial peripheral interface), 537–540

synchronizers, 32, 48–53

tapped delay line, 41–42

TMDS link, 559–562

VGA video interface, 551–554

Circuits, sequential (VHDL examples). See list in Appendix M, 571–579 (examples, 6.2, 6.3, 12.1 to 12.10, 13.2 to 13.4, 17.1 to 17.8, 18.3)

Circuits, sequential (VHDL exercises). See list in Appendix M, 571–579 (exercises, 13.1 to 13.3, 13.7 to 13.44, 17.1 to 17.19, 18.3, 18.4)

Circuits, state machines (review)

arbiter, 89

car alarm, 93–94

datapath controller for a serial multiplier, 97–99

datapath for greatest common divisor, 90–93

garage door controller, 89–90

password decoder, 94–95

reference-value with embedded debouncers, 99–100

SPI interface for an A/D converter, 95–96

SRAM memory interface, 97

Circuits, state machines (VHDL examples). See list in Appendix M, 571–579 (examples, 16.1 to 16.6)

Circuits, state machines (VHDL exercises). See list in Appendix M, 571–579 (exercises, 15.1 to 15.3, 16.1 to 16.12)

clk’event versus rising_edge(clk), 284–285

Clock-domain crossing, 49–52

Clock gating, 53–54

Code structure, 129–130

Combinational circuits. See Circuits, combinational (review); Circuits, combinational (VHDL examples); Circuits, combinational (VHDL exercises)

Combinational circuits review, 1–16

Combinational loops, 312

Comments, 120

Comparison operators, 223–226

Component, 252–256

component instantiation, 252–253

concept, 252

design entity instantiation, 253–256

generic map, 252, 254

port map, 252, 254–256

Concatenation. See Array manipulation

Condition operator, 227–228

Constant, 139–140

Deferred constant, 350

Delimiters, 122

Design flow, 117

Design of arithmetic circuits, 259–264

Entity-architecture binding, 145

Entity declaration, 135–137

‘event attribute. See Attributes

exit statement. See Statements

Extension, truncation, rounding, and saturation, 21–24

falling_edge function, 284–285

File, 142, 490–495

Finite state machines. See State machines review; State machines VHDL

Fixed-point types. See Types

float, 169

float32, 169

float64, 170

float128, 170

Floating-point arithmetic review, 24–28

Floating-point types. See Types

FPGAs review, 101–114

configuration memory, 103–104

CPLDs, 105–107

FPGA families, 109–113

PAL, PLA, GAL, 104–105

programmable logic devices, 101–103

what is an FPGA, 107–109

fsm_encoding attribute. See Attributes

fsm_safe_state attribute. See Attributes

Function. See Subprograms

Functional simulation. See Simulation

Function versus procedure. See Subprograms

generate statement. See Statements

Generic map. See Component

Generics, 135–137, 143–144

Glitch analysis and prevention, 34–35, 531–532

Group, 237–238

Identifiers, 122

if statement. See Statements

inout mode, 135

integer, 161

Integer arithmetic review, 16–24

integer_vector, 162

keep attribute. See Attributes

Lexical elements, 120

Libraries description, 130–132

Libraries list, 132

Library declaration, 134

Logical operators, 214–217

loop statement. See Statements

Metastability, 32

minimum and maximum functions, 223, 226

Mistakes in assignments, 201–204

mod operator. See Operators, arithmetic

Naming suggestions, 123–127

nand operator. See Operators, logical

natural, 161

next statement. See Statements

nor operator. See Operators, logical

not operator. See Operators, logical

null statement. See Statements

Object classes, 138–142

Operators, 213–229

arithmetic, 218–223

comparison, 223–226

concatenation, 175, 227

condition, 227–228

logical, 214–217

overloaded, 228–229

predefined, 213–228

shift, 226–227

or operator. See Operators, logical

others keyword, 173–174

Overflow flag, 16–20

Overloaded operators, 228–229

Packages, 131–134, 349–353

official packages, 132

package body, 350

package construction, 349–351

package declaration in the code, 134

package with deferred constant, 350

package with generics, 351–353

package with subprogram, 350–351

Port, 135

Port map. See Component

positive, 161

Predefined types. See Types

Procedure. See Subprograms

Process, 255–258

Qualifying expressions, 183

‘quiet attribute. See Attributes

real, 155, 170–172

real_vector, 155

Register transfer level (RTL), 35–36

rem operator. See Operators, arithmetic

report statement. See Statements

Reserved VHDL words, 123

resize function. See Array manipulation

Review of combinational circuits, 1–16

Review of finite state machines, 67–100

Review of floating-point arithmetic, 24–28

Review of FPGAs, 101–114

Review of integer arithmetic, 16–24

Review of sequential circuits, 29–66

rising_edge function, 284–285

rol operator. See Operators, shift

ror operator. See Operators, shift

Rounding, 21–24, 26–28

RTL, 35–36

safe attribute. See Attributes

select statement. See Statements

select? statement. See Statements

Sequential circuits. See Circuits, sequential (review); Circuits, sequential (VHDL examples); Circuits, sequential (VHDL exercises)

Sequential circuits review, 29–66

Severity level, 367

sfixed, 168

Shift operators, 226–227

Signal, 140–141, 298–301

Signal versus variable, 298–301

signed, 166

Signed and unsigned types. See Types

Signed integer arithmetic, 19–21

Simulation, 117–118, 475–495, 531–532

complete testbenches, 482–489

dealing with data files, 490–495

dealing with time, 476–478

functional simulation, 117–118, 531–532

stimuli generation, 478–481

testbenches, 475–476

timing simulation, 117–118, 531–532

using Tcl scripts, 504, 508, 527, 528

sla operator. See Operators, shift

sll operator. See Operators, shift

sra operator. See Operators, shift

srl operator. See Operators, shift

’stable attribute. See Attributes

Standard-logic types. See Types

Standard types. See Types

State machine circuits. See Circuits, state machines (review); Circuits, state machines (VHDL examples); Circuits, state machines (VHDL exercises)

State machines review

choosing between Moore and Mealy, 73–74

dealing with recursivity, 86–88

dealing with repetitive states, 85–86

dealing with time, 83–85

examples and exercises, 88–100

fundamental design technique, 79–82

machine categories (regular, timed, recursive), 82–83

machine types (Moore, Mealy), 68–69

Moore-Mealy conversion, 70–71

number of flip-flops, 88

pointer-based implementation, 86–87

state encoding styles, 39–41, 79, 375

state transition diagram, 68–69, 75–77

time behavior, 71–73

transition types, 74–75

State machines VHDL, 373–405, 407–426

circuits (see Circuits, state machines [review]; Circuits, state machines [VHDL examples]; Circuits, state machines [VHDL exercises])

design summary, 373–375, 402

fsm_encoding (see Attributes)

fsm_safe_state (see Attributes)

practice with VHDL, 407–426

safe (see Attributes)

state encoding styles, 375

syn_encoding (see Attributes)

templates for recursive machines, 395–402

templates for regular machines, 375–387

templates for timed machines, 387–394

Statements

assert, 367–368

case, 291–294

case?, 294

component instantiation (see Component)

concurrent statements, 243–252

exit, 296–297

generate, 249–252

if, 288–291

loop, 295–297

next, 296–297

null, 247, 289

process, 255–258

report, 368

return, 354

select, 248–249

select?, 248

sequential, 283–284, 288–298

wait, 294–295

when, 246–247

std_logic, 163

std_logic_vector, 164

std_ulogic, 163

std_ulogic_vector, 164

Strength-stripping functions, 182

string, 162

Subprograms, 353–366

function, 353–359

function versus procedure, 363

generic subprograms, 364–366

overloaded subprograms, 228–229

procedure, 359–362

syn_encoding attribute. See Attributes

Synchronizers, 32, 48–53

Testbenches. See Simulation

time, 155, 476–478

time_vector, 155

Timing simulation. See Simulation

To_01 function, 182

to_bit function, 180

to_bitvector function, 180

to_float function, 180

to_integer function, 180

to_sfixed function, 180

to_signed function, 180

to_std_logic_vector function, 180

to_stdlogicvector function, 180

to_stdulogic function, 180

to_std_ulogic_vector function, 180

to_stdulogicvector function, 180

to_string function, 368–369

to_ufixed function, 180

to_unsigned function, 180

To_X01 function, 182

Type conversion, 178–182

automatic conversion, 178–179

conversion functions, 179–182

strength-stripping functions, 182

type cast, 179

Type declarations. See Types

Types

array types (see Arrays)

enumeration types, 155, 195

file types, 142, 490–495

fixed-point types, 166–168

floating-point types, 168–170

integer types, 155, 161–162, 193–194

predefined types, 153–154, 159–171, 197

record types, 172–173

standard-logic types, 162–164

standard types, 160–162

subtypes, 158–159

synthesizable types, 154

type conversion, 178–182

type declarations, 156–158, 193, 195

type qualifying expressions, 183

unsigned and signed types, 165–166

user-defined types, 193–199

ufixed, 167

unaffected keyword, 247, 293

unsigned, 165

Unsigned and signed types. See Types

Unsigned integer arithmetic, 17–19

User-defined types. See Types

Variable, 141–142, 298–301

VHDL, about, 115

VHDL libraries, 130–133

VHDL packages, 131–133

VHDL reserved words, 123

VHDL tools, 117

wait statement. See Statements

when statement. See Statements

xnor operator. See Operators, logical

xor operator. See Operators, logical