The Designer's Guide to VHDL
Author:
Peter J. Ashenden and Jim Lewis
Pub Year:
2008
Source:
Read: 2018-02-01
Last Update: 2018-02-01
Five Sentence Abstract:
The Designer's Guide starts off with pretty basic concepts such as data types and logic, both combinational and sequential. Next it moves into modeling, procedures, functions, components, aliasing, generate, and generic statements. It discusses a number of more exotic features, like configurations as well as access types that have limitations, if even available, during synthesis. Included are a few case studies, one on DSP pipelining, another on memory design, and a final one on the gumnut, a simple soft core processor. All in all it is a great reference manual for VHDL, assuming you already have knowledge of the digital design process and know what you want to implement.
Thoughts:
The main shortcoming is that there is, at least in my digital copy, no table of
contents. This is rather horrible considering that it is such a great
reference.
It covers, in depth, many, if not most, of constructs you would use in VHDL.
Beyond what you would commonly use, it also covers the more esoteric topics
like encrypting intellectual property.
Some of the topics are not of use to a hobbyist level tinkerer. In particular
some of the high performance timing and simulation only features would unlikely
be used by anyone but a professional. Still, they are worthwhile reading over
to give a better understanding of the language's full capability.
Each chapter ends with a number of questions, ranked in difficulty between 1
and 5. The difficulty 1 questions are answered in an appendix. This is an
extremely nice feature for someone learning on their own.
Notes:
Table of Contents
01: Fundamental Concepts
02: Scalar Data Types and Operations
03: Sequential Statements
04: Composite Data Types and Operations
05: Basic Modeling Constructs
06: Subprograms
07: Packages and Use Clauses
08: Resolved Signals
09: Predefined and Standard Packages
10: Pipelined Multiplier Accumulator
11: Aliases
12: Generics
13: Components and Configurations
14: Generate Statements
15: Access Types
16: Files and Input/Output
17: Case Study: A Package for Memories
18: Test Bench and Verification Features
19: Shared Variables and Protected Types
20: Attributes and Groups
21: Design for Synthesis
22: Case Study: System Design Using the Gumnut Core
23: Miscellaneous Topics
page 11:
- At the most abstract level, the function of the entire system may be
described in terms of an algorithm, much like an algorithm for a computer
program. This level of functional modeling is often called behavioral modeling
page 12:
page 26:
- // Can comment with
--
or /* */
page 30:
- Both integer and real literals can also use exponential notation, in which
the number is followed by the letter ‘E’ or ‘e’, and an exponent value. This
indicates a power of 10 by which the number is multiplied. For integer
literals, the exponent must not be negative, whereas for real literals, it may
be either positive or negative. Some examples of integer literals using
exponential notation are
- Some examples of real literals using exponential notation are
| 1.234E09 98.6E+21 34.0e-08
|
page 31:
- If we need to include a double quotation mark character in a string, we write
two double quotation mark characters together. The pair is interpreted as just
one character in the string. For example:
| "A string in a string: ""A string"". "
|
- If we need to write a string that is longer than will fit on one line, we can
use the concatenation operator (“&”) to join two substrings together. (This
operator is discussed in Chapter 4.) For example:
| "If a string will not fit on one line, "
& "then we can break it into parts on separate lines."
|
page 33:
- Note that expansion of non-digit characters does not extend to em- bedded
underscores, which we might add for readability. Thus, O"3_X" represents
"011XXX", not "011___XXX".
page 43:
- suppose we wish to define an adder entity that adds small integers in the
range 0 to 255. We write a package containing the type declaration, as follows:
| package int_types is
type small_int is range 0 to 255;
end package int_types;
|
- This defines a package named int_types, which provides the type named
small_int. The package is a separate design unit and is analyzed before any
entity declaration that needs to use the type it provides. We can use the type
by preceding an entity declaration with a use clause, for example:
| use work.int_types.all;
entity small_adder is
port (a, b : in small_int;
s : out small_int
);
end entity small_adder;
|
page 45:
| maximum(3, 20) = 20
minimum(3, 20) = 3
|
page 57:
- the expression '0' = 'L' yields false, even though both values represent low
logic levels. If we want to compare logic levels without taking account of
drive strength, we should use the matching operators “?=” and “?/=”. These
operators perform logical equivalence and unequivalence comparisons,
respectively. If both operands are ‘0’, ‘1’, ‘L’, or ‘H’, the operations yield
a ‘0’ or ‘1’ result. For example:
| '1' ?= 'H' = '1'
'0' ?= 'H' = '0'
'1' ?/= 'H' = '0'
'0' ?/= 'H' = '1'
|
- The “?=” and “?/=” operators yield ‘X’ when either operand is ‘X’, ‘Z’, or
‘W’. However, if either operand is ‘U’, the result is ‘U’. The final exception
is for don’t care (‘–’) operands. For these, “?=” always yeilds ‘1’ and “?/=”
always yields ‘0’. Some examples are:
| 'Z' ?= 'H' = 'X'
'0' ?= 'U' = 'U'
'1' ?= '-' = '1'
'W' ?/= 'H' = 'X'
'0' ?/= 'U' = 'U'
‘-' ?/= '-' = '0'
|
- We note briefly here, for completeness, that VHDL also defines the matching
operators “?<”, “?<=”, “?>” and “?=>” for comparing std_ulogic values. They
treat a logic low level (‘0’ or ‘L’) as being less than a logic high level (‘1’
or ‘H’), yield ‘X’ for comparison with a non-logic-level value other than ‘U’,
and yield ‘U’ for comparison with ‘U’. Comparison with ‘–’ is not allowed.
page 59:
page 62:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | T'left first (leftmost) value in T
T'right last (rightmost) value in T
T'low least value in T
T'high greatest value in T
T'ascending true if T is an ascending range, false otherwise
T'image(x) a string representing the value of x
T'value(s) the value in T that is represented by s
T'pos(x) position number of x in T
T'val(n) value in T at position n
T'succ(x) value in T at position one greater than that of x
T'pred(x) value in T at position one less than that of x
T'leftof(x) value in T at position one to the left of x
T'rightof(x) value in T at position one to the right of x
T'base For any subtype T, this attribute produces the base type of T
|
page 92:
| for count_value in 0 to 127 loop
count_out <= count_value;
wait for 5 ns;
end loop;
|
-
the identifier count_value takes on the values 0, 1, 2 and so on, and for
each value, the assignment and wait statements are executed. Thus the signal
count_out will be assigned values 0, 1, 2 and so on, up to 127, at 5 ns
intervals.
-
// Use this in a test bench to produce a sequential
input.
page 111:
| A'left(N) Left bound of index range of dimension N of A
A'right(N) Right bound of index range of dimension N of A
A'low(N) Lower bound of index range of dimension N of A
A'high(N) Upper bound of index range of dimension N of A
A'range(N) Index range of dimension N of A
A'reverse_ range(N) Reverse of index range of dimension N of A
A'length(N) Length of index range of dimension N of A
A'ascending(N) true if index range of dimension N of A is an ascending range, false otherwise
A'element The element subtype of A
|
page 113:
- The symbol “<>”, often called “box,” can be thought of as a placeholder for the index
range, to be filled in later when the type is used. An example of an unconstrained array
type declaration is:
| type sample is array (natural range <>) of integer;
|
- An important point to understand about unconstrained array types is that when
we declare an object of such a type, we need to provide a constraint that
specifies the index bounds. We can do this in several ways. One way is to
provide the constraint when an object is created, for example:
| variable short_sample_buf : sample(0 to 63);
-- or
subtype long_sample is sample(0 to 255);
variable new_sample_buf, old_sample_buf : long_sample;
-- or
constant lookup_table : sample:= ( 1 => 23, 3 => -16, 2 => 100, 4 => 11);
|
page 118:
| variable main_sample_set : sample_set(1 to 100)(1 to 20);
|
- The first index range, 1 to 100, is used for the top level of the type's
hierarchy, and the second index range, 1 to 20, is used for the second level.
| variable bakers_samples : dozen_samples(open)(0 to 9);
|
- In this example, we write open for the top-level index range, since the type
itself specifies 1 to 12 for that index range. Using the word open allows us to
advance to the second- level index range, where we specify 0 to 9 as the
constraint.
page 119:
| constant default_sample_pair : sample_set
:= (1 => (39, 25, 6, -4, 5),
2 => (9, 52, 100, 0, -1),
3 => (0, 0, 0, 0, 0)
);
|
- the top-level index range is inferred to be 1 to 3 (from the choices in the
outer level of the aggregate), and the second-level index range is inferred to
be 0 to 4
page 136:
- Whole record values can be assigned using assignment statements, for example:
We can also refer to an element in a record using a selected name, for example:
| type time_stamp is record
seconds : integer range 0 to 59;
minutes : integer range 0 to 59;
hours : integer range 0 to 23;
end record time_stamp;
variable sample_time, current_time : time_stamp;
sample_time := current_time;
sample_hour := sample_time.hours;
|
page 139:
- We can also use named association, in which we identify each element in the
aggregate by its name. The order of elements identified using named association
does not affect the aggregate value. The example above could be rewritten as:
| constant midday : time_stamp := (0, 0, 12);
constant midday : time_stamp := (hours => 12, minutes => 0, seconds => 0);
|
page 156:
- Given a signal S, and a value T of type time, VHDL defines the following
attributes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | S'delayed(T) A signal that takes on the same values as S but is delayed
by time T.
S'stable(T) A Boolean signal that is true if there has been no event on S
in the time interval T up to the current time, otherwise false.
S'quiet(T) A Boolean signal that is true if there has been no transaction
on S in the time interval T up to the current time, otherwise
false.
S'transaction A signal of type bit that changes value from ‘0’ to ‘1’ or vice
versa each time there is a transaction on S.
S'event True if there is an event on S in the current simulation cycle,
false otherwise.
S'active True if there is a transaction on S in the current simulation
cycle, false otherwise.
S'last_event The time interval since the last event on S.
S'last_active The time interval since the last transaction on S.
S'last_value The value of S just before the last event on S.
|
page 172:
| next_state_logic : process (all) is
begin
...
end process next_state;
|
- // ALL = all the input signals in the process.
page 173:
| PC_incr : next_PC <= PC + 4 after 5 ns;
|
- At first sight this appears to be an ordinary
sequential signal assignment statement, which by rights ought to be
inside a process body. However, if it appears as a concurrent statement in an
architecture body, it is equivalent to the following
process statement:
| PC_incr : process is
begin
next_PC <= PC + 4 after 5 ns;
wait on PC;
end process PC_incr;
|
- Looking at the equivalent process shows us something important about the
concurrent signal assignment statement, namely, that it is sensitive to the PC
signal. In fact, a concurrent signal assignment is sensitive to all of the
signals mentioned in the waveform. So whenever any of those signals change
value, the assignment is reevaluated and a new transaction is scheduled on the
driver for the target signal.
page 176:
| reset_gen : reset <= '1', '0' after 200 ns when extended_reset else
'1', '0' after 50 ns;
|
- The thing to note here is that there are no signals named in any of the
waveforms or the conditions (assuming that extended_reset is a constant). This
means that the statement is executed once when simulation starts, schedules two
transactions on reset and remains quiescent thereafter. The equivalent process
is:
| reset_gen : process is
begin
if extended_reset then
reset <= '1', '0' after 200 ns;
else
reset <= '1', '0' after 50 ns;
end if;
wait;
end process reset_gen;
|
- Since there are no signals involved, the wait statement has no sensitivity
clause. Thus, after the if statement has executed, the process suspends
forever.
page 179:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | entity full_adder is
port (a, b, c_in : bit;
s, c_out : out bit );
end entity full_adder;
architecture truth_table of full_adder is
begin
with bit_vector'(a, b, c_in) select
(c_out, s) <= bit_vector'("00") when "000",
bit_vector'("01") when "001",
bit_vector'("01") when "010",
bit_vector'("10") when "011",
bit_vector'("01") when "100",
bit_vector'("10") when "101",
bit_vector'("10") when "110",
bit_vector'("11") when "111";
end architecture truth_table;
|
- This example illustrates the most common use of aggregate targets in signal
assignments. Note that the type qualification is required in the selector
expression to specify the type of the aggregate.
page 181:
| check : assert not (s and r)
report "Incorrect use of S_R_flip_flop: " & "s and r both '1'";
|
- // Concurrent above and process below produce the same
results.
| check : process is
begin
assert not (s and r)
report "Incorrect use of S_R_flip_flop: " & "s and r both '1'";
wait on s, r;
end process check;
|
page 182:
- We can rewrite the entity declaration for the set/reset flipflop of Example
5.19 as shown below, using a concurrent assertion statement for the usage
check. If we do this, the check is included for every possible implementation
of the flipflop and does not need to be included in the corresponding
architecture bodies.
| entity S_R_flipflop is
port (s, r : in bit;
q, q_n : out bit );
begin
check : assert not (s and r)
report "Incorrect use of S_R_flip_flop: s and r both '1'";
end entity S_R_flipflop;
|
- The following entity declaration for a read-only memory (ROM) includes a
passive process, trace_reads, that is sensitive to changes on the enable port.
When the value of the port changes to ‘1’, the process reports a message
tracing the time and address of the read operation. The process does not affect
the course of the simulation in any way, since it does not include any signal
assignments.
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | entity ROM is
port (address : in natural;
data : out bit_vector(0 to 7);
enable : in bit );
begin
trace_reads : process (enable) is
begin
if enable then
report "ROM read at time " & to_string(now)
& " from address "
& to_string(address);
end if;
end process trace_reads;
end entity ROM;
|
page 213:
- a procedure encapsulates a collection of sequential statements that are
executed for their effect, whereas a function encapsulates a collection of
statements that compute a result. Thus a procedure is a generalization of a
statement, whereas a function is a generalization of an expression.
page 214:
| procedure average_samples is
variable total : real := 0.0;
begin
assert samples'length > 0 severity failure;
for index in samples'range loop
total := total + samples(index);
end loop;
average := total / real(samples'length);
end procedure average_samples;
|
page 234:
- Since a function is called as part of evaluation of
an expression, a function is not allowed to include a wait statement
(nor call a procedure that includes a wait statement). Expressions must always
be evaluated within a single simulation cycle.
page 235:
- The following function calculates whether a value is within given bounds and
returns a result limited to those bounds.
| function limit ( value, min, max : integer ) return integer is
begin
if value > max then
return max;
elsif value < min then
return min;
else
return value;
end if;
end function limit;
|
- A call to this function might be included in a variable assignment statement,
as follows:
| new_temperature := limit (current_temperature + increment, 10, 100);
|
page 236:
-
function may not refer to any variables or signals declared by its parents,
that is, by any process, subprogram or architecture body in which the function
declaration is nested. Otherwise the variables or signals might change values
between calls to the function,
-
we may deliberately relax the restriction about a function referencing its
parents’ variables or signals by including the keyword impure in the function declaration. This is a warning
to any caller of the function that it might produce different results on
different calls, even when passed the same actual parameter values.
page 237:
- We can use an impure function to generate sequence numbers when creating
packets in a behavioral model of a network interface. The following is an
outline of a process that represents the output side of the network interface.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | network_driver : process is
constant seq_modulo : natural := 2**5;
subtype seq_number is natural range 0 to seq_modulo-1;
variable next_seq_number : seq_number := 0;
...
impure function generate_seq_number return seq_number is
variable number : seq_number;
begin
number := next_seq_number;
next_seq_number := (next_seq_number + 1) mod seq_modulo;
return number;
end function generate_seq_number;
begin -- network_driver
...
new_header := pkt_header'( dest => target_host_id,
src => my_host_id,
pkt_type => control_pkt,
seq => generate_seq_number );
...
end process network_driver;
|
page 282:
- Resolved signals of resolved subtypes are the only means by which we may
connect a number of sources together, since we need a resolution function to
determine the final value of the signal or port from the contributing values.
The resolution function must take a single parameter that is a one-dimensional
unconstrained array of values of the signal type, and must return a value of
the signal type. The index type of the array does not matter, so long as it
contains enough index values for the largest possible collection of sources
connected together.
page 302:
page 304:
page 305:
page 311:
page 312:
| signal inc_en : std_ulogic;
signal inc_reg : unsigned(7 downto 0);
...
inc_reg_proc : process (clk) is
begin
if rising_edge(clk) then
inc_reg <= inc_reg + inc_en;
end if;
end process inc_reg_proc;
|
- If we had written the if statement as follows:
| if inc_en = '1' then
inc_reg <= inc_reg + 1;
end if;
|
- a synthesis tool might have generated an adder with the vector "00000001" as
an in- put, connected to a register with clock enable. By using the control
signal as an operand, we more clearly imply an incrementer and a simple
register without clock enable.
page 314:
page 319:
-
// Decent description of fix point math.
-
The whole-number part of the value is on the left of the vector, down to
index 0, and the fractional part is on the right, starting at index –1. For
example, given the following declaration of a fixed-point signal A:
| signal A : ufixed(3 downto -3) := "0110100";
|
- the whole-number part is A(3 downto 0), and the fractional part is A(–1
downto –3). The range of values represented is 0 to just less than 16 in steps
of 0.125 (one-eighth). The value represented by the default initial value is
0110.1002 = 6.5_10.
page 320:
- The fixed-point math packages perform operations with full precision. This is
illustrated in the following example:
| signal A4_2 : ufixed(3 downto -2);
signal B3_3 : ufixed(2 downto -3);
signal Y5_3 : ufixed(4 downto -3);
...
Y5_3 <= A4_2 + B3_3;
|
-
The whole-number part of the addition result is one bit larger than the
larger of the two operand whole-number parts. In this example, the operand
whole-number parts are 4 bits and 3 bits, respectively, so the result's
whole-number part is 5 bits. The fractional part of the result is the larger
fractional part of the operands. In this example, the operands’ fractional
parts are 2 bits and 3 bits, respectively, so the result has a 3-bit fractional
part.
-
If we want to assign a fixed-point value to an object, one way is to use a string literal,
for example:
| signal A4 : ufixed(3 downto -3);
...
A4 <= "0110100"; -- string literal for 6.5
|
- Alternatively, we can apply a conversion function, to_ufixed or to_sfixed, to
an integer or real value. In this case, we need to specify the index range for
the conversion result. There are two forms of conversion function. For the
first form, we specify the left and right indices for the result, for example:
| A4 <= to_ufixed(6.5, 3, -3); -- pass indices
|
- For the second form, we provide an object whose index range is used:
| A4 <= to_ufixed(6.5, A4); -- sized by A4
|
page 327:
- Operator Overloading Summary // Charts
page 328:
page 330:
Conversion Function Summary // Charts
page 350:
page 360:
1
2
3
4
5
6
7
8
9
10
11
12 | library ieee; use ieee.std_logic_1164.all;
use work.alu_types.all, work.io_types.all;
architecture structural of controller_system is
alias alu_data_width is work.alu_types.data_width;
alias io_data_width is work.io_types.data_width;
signal alu_in1, alu_in2, alu_result: std_ulogic_vector(0 to alu_data_width - 1);
signal io_data: std_ulogic_vector(0 to io_data_width - 1);
...
begin
...
end architecture structural;
|
page 361:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 | type register_array is array (0 to 15) of bit_vector(31 downto 0);
type register_set is record
general_purpose_registers : register_array;
program_counter : bit_vector(31 downto 0);
program_status : bit_vector(31 downto 0);
end record;
variable CPU_registers : register_set;
-- we can declare aliases for the record elements:
alias PSW is CPU_registers.program_status;
alias PC is CPU_registers.program_counter;
alias GPR is CPU_registers.general_purpose_registers;
-- We can also declare aliases for individual registers in the register array, for example:
alias SP is CPU_registers.general_purpose_registers(15);
-- The name that we are aliasing can itself be an alias. Hence the alias
-- declaration for SP can be written using the alias name GPR:
alias SP is GPR(15);
-- An alias can also be used to denote a slice of a one-dimensional array. For
-- example, given the above declaration for CPU_registers, we can declare an alias
-- for part of the pro- gram status register:
alias interrupt_level is PSW(30 downto 26);
|
| alias interrupt_level : bit_vector(4 downto 0) is PSW(30 downto 26);
|
-- then interrupt_level(4) would denote PSW(30), interrupt_level(3) would
denote PSW(29), and so on.
page 364:
- the only kinds of items for which we cannot declare aliases are labels, loop
parameters and generate parameters
page 365:
- If we define an alias for an enumeration type, all of the enumeration
literals of the original type are available for use. We do not need to define
aliases for the literals, nor use fully selected names. For example, if a
package system_types declares an enumeration type as follows:
| type system_status is (idle, active, overloaded);
-- and a model defines an alias for this type:
alias status_type is work.system_types.system_status;
|
- the model can simply refer to the literals idle, active and overloaded,
instead of work.system_types.overloaded and so on.
page 374:
- whenever the sizes of different array ports of an entity are related, generic
constants should be considered to enforce the constraint.
| entity reg is
generic (width : positive );
port (d : in bit_vector(0 to width - 1);
q : out bit_vector(0 to width - 1);
clk, reset : in bit );
end entity reg;
word_reg : entity work.reg(behavioral)
generic map ( width => 32 )
port map ( ... );
|
page 377:
| entity generic_mux2 is
generic (type data_type );
port(sel: in bit;
a, b: in data_type;
z : out data_type);
end entity generic_mux2;
architecture rtl of mux2 is
begin
z <= a when not sel else b;
end architecture rtl;
|
| signal sel_bit, a_bit, b_bit, z_bit : bit;
...
bit_mux : entity work.generic_mux2(rtl)
generic map (data_type => bit )
port map(sel => sel_bit,
a => a_bit,
b => b_bit,
z => z_bit);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | type msg_packet is record
src, dst : unsigned(7 downto 0);
pkt_type : bit_vector(2 downto 0);
length : unsigned(4 downto 0);
payload : byte_vector(0 to 31);
checksum : unsigned(7 downto 0);
end record msg_packet;
signal pkt_sel : bit;
signal pkt_in1, pkt_in2, pkt_out : msg_pkt;
...
pkt_mux : entity work.generic_mux2(rtl)
generic map ( data_type => msg_packet )
port map(sel => pkt_sel,
a => pkt_in1,
b => pkt_in2,
z => pkt_out );
|
page 390:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 | package bounded_buffer_adt is
generic ( size : positive;
type element_type );
type store_array is array (0 to size - 1) of element_type;
type bounded_buffer is record
count : natural;
head, tail : natural;
store : store_array;
end record bounded_buffer;
procedure reset ( b : inout bounded_buffer );
-- resets a bounded buffer to be empty
function is_empty ( b : bounded_buffer ) return boolean;
-- tests whether the bounded buffer is empty
-- (i.e., no data to read)
function is_full ( b : bounded_buffer ) return boolean;
-- tests whether the bounded buffer is full
--(i.e., no data can be written)
procedure write ( b : inout bounded_buffer;
data : in
element_type );
-- if the bounded buffer is not full, writes the data
-- if it is full, assertion violation with severity failure
procedure read ( b : inout bounded_buffer;
data : out element_type );
-- if the bounded buffer is not empty, read the first item
-- if it is empty, assertion violation with severity failure
end package bounded_buffer_adt;
|
page 391:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | receiver : process is
subtype byte is bit_vector(0 to 7);
package byte_buffer_adt is new work.bounded_buffer_adt
generic map ( size => 2048, element_type => byte );
use work.byte_buffer_adt.all;
variable receive_buffer : bounded_buffer;
...
begin
reset(receive_buffer);
...
if is_full(receive_buffer) then
... -- buffer overrun
else
write(receive_buffer, received_byte);
end if;
...
if is_empty(receive_buffer) then
... -- buffer underrun
else
read(receive_buffer, check_byte);
end if;
...
end process receiver;
|
page 392:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 | package body bounded_buffer_adt is
procedure reset ( b : inout bounded_buffer ) is
begin
b.count := 0; b.head := 0; b.tail := 0;
end procedure reset;
function is_empty ( b : bounded_buffer ) return boolean is
begin
return b.count = 0;
end function is_empty;
function is_full ( b : bounded_buffer ) return boolean;
begin
return b.count = size;
end function is_full;
procedure write ( b : inout bounded_buffer;
data : in element_type ) is
begin
if is_full(b) then
report "write to full bounded buffer" severity failure;
else
b.store(b.tail) := data;
b.tail := (b.tail + 1) mod size;
b.count := b.count + 1;
end if;
end procedure write;
procedure read ( b : inout bounded_buffer;
data : out element_type ) is
begin
if is_empty(b) then
report "read from empty bounded buffer" severity failure;
else
data := b.store(b.head);
b.head := (b.head + 1) mod size;
b.count := b.count - 1;
end if;
end procedure read;
end package body bounded_buffer_adt;
|
page 393:
- an ADT user can make use of the information to modify the data structures
without using the ADT procedures and functions. For example, if an ADT
operation simply updates a record element, a user might be tempted to update
the record directly and avoid the overhead of a procedure call. However, modern
compilers and computers make such “optimizations” unnecessary, and the risk is
that the user might inadvertently corrupt the data structure. ADTs in VHDL
require that users avoid such temptations and abide by the contract expressed
in the ADT interface. A small amount of self-discipline here will yield
significant benefits in the modeling process.
page 395:
- we can write a swap procedure with the type as a formal generic, as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | procedure swap
generic ( type T )
parameter ( a, b : inout T ) is
variable temp : T;
begin
temp := a; a := b; b := temp;
end procedure swap;
-- We can now instantiate the procedure to get versions for various types:
procedure int_swap is new swap
generic map ( T => integer );
procedure vec_swap is new swap
generic map ( T => bit_vector(0 to 7) );
-- and call them to swap values of variables:
variable a_int, b_int : integer;
variable a_vec, b_vec : bit_vector(0 to 7);
...
int_swap(a_int, b_int);
vec_swap(a_vec, b_vec);
|
page 399:
- In Example 12.6 on page 375, we attempted to define a counter that could
count with a variety of types. However, our attempt failed because we could not
use the “+” operator to increment the count value. We can rectify this by
declaring a formal generic function for incrementing the count value:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 | entity generic_counter is
generic (type count_type;
constant reset_value : count_type;
function increment (x : count_type) return count_type
);
port (clk, reset : in bit;
data : out count_type );
end entity generic_counter;
-- We can then use the increment function in the architecture:
architecture rtl of generic_counter is
begin
count : process (clk) is
begin
if rising_edge(clk) then
if reset then
data <= reset_value;
else
data <= increment(data);
end if;
end if;
end process count;
end architecture rtl;
-- Having revised the counter in this way, we can instantiate it with various
-- types. For example, to create a counter for unsigned values, we define a
-- function, add1, to increment using the “ +” operator on unsigned values and
-- provide it as the actual for the increment generic.
use ieee.numeric_std.all;
function add1 ( arg : unsigned ) return unsigned is
begin
return arg + 1;
end function add1;
signal clk, reset : bit;
signal count_val : unsigned(15 downto 0);
...
counter : entity work.generic_counter(rtl)
generic map (count_type => unsigned(15 downto 0),
reset_value => (others => '0'),
increment => add1 ) -- add1 is the
-- actual function
port map ( clk => clk, reset => reset, data => count_val );
|
page 400:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | type traffic_light_color is (red, yellow, green);
function next_color ( arg : traffic_light_color )
return traffic_light_color is
begin
if arg = traffic_light_color'high then
return traffic_light_color'low;
else
return traffic_light_color'succ(arg);
end if;
end function next_color;
signal east_light : traffic_light_color;
...
east_counter : work.generic_counter(rtl)
generic map ( count_type => traffic_light_color,
reset_value => red,
increment => next_color ) -- next_color is the
-- actual function
port map ( clk => clk, reset => reset, data => east_light );
|
page 460:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | library ieee; use ieee.std_logic_1164.all;
entity shift_reg is
port (phi1, phi2 : in std_ulogic;
serial_data_in : in std_ulogic;
parallel_data : out std_ulogic_vector );
end entity shift_reg;
architecture cell_level of shift_reg is
alias normalized_parallel_data: std_ulogic_vector(0 to parallel_data'length - 1)
is parallel_data;
component master_slave_flipflop is
port (phi1, phi2 : in std_ulogic;
d : in std_ulogic;
q : out std_ulogic );
end component master_slave_flipflop;
begin
reg_array : for index in normalized_parallel_data'range generate
begin
reg: if index = 0 generate
cell: component master_slave_flipflop
port map (phi1, phi2,
d => serial_data_in,
q => normalized_parallel_data(index) );
else generate
cell: component master_slave_flipflop
port map (phi1, phi2,
d => normalized_parallel_data(index - 1),
q => normalized_parallel_data(index) );
end generate other_cell;
end generate reg_array;
end architecture cell_level;
|
page 483:
- access types. These are similar to pointer
types found in many programming languages.
page 484:
- Note that we cannot declare constants or signals of access types. Variables
are the only class of object that may be of an access type.
page 485:
-
This statement has the combined effects of creating and initializing the data
object and assigning a pointer to it to the variable count, as shown in Figure
15.1(c). The pointer overwrites the null pointer previously stored in count.
-
we can update the object’s value as follows:
| count.all := 10;
-- and we use its value in an expression:
if count.all = 0 then
...
end if;
|
| count := new natural;
count.all := 10;
-- we could achieve the same effect with this second form of allocator:
count := new natural'(10);
|
1
2
3
4
5
6
7
8
9
10
11
12
13 | type stimulus_record is record
stimulus_time : time;
stimulus_value : bit_vector(0 to 3);
end record stimulus_record;
type stimulus_ptr is access stimulus_record;
-- and an access variable declared as
variable bus_stimulus : stimulus_ptr;
-- we could create a new stimulus record data object and set bus_stimulus to
point to it as follows:
bus_stimulus := new stimulus_record'( 20 ns, B"0011" );
|
page 490:
page 495:
page 503:
- A file can only contain one type of object, but that type can be almost any
VHDL type, including scalar types, records and one-dimensional arrays. The only
types that cannot be stored in files are multidimensional arrays, access types,
protected types and other files.
page 506:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | architecture behavioral of ROM is
begin
behavior : process is
subtype word is std_ulogic_vector(0 to data'length - 1);
type storage_array is array (natural range 0 to 2**address'length - 1) of word;
variable storage : storage_array;
variable index : natural;
... -- other declarations
type load_file_type is file of word;
file load_file : load_file_type
open read_mode is load_file_name;
begin
-- load ROM contents from load_file
index := 0;
while not endfile(load_file) loop
read(load_file, storage(index));
index := index + 1;
end loop;
-- respond to ROM accesses
loop
...
end loop;
end process behavior;
end architecture behavioral;
|
page 512:
- One important point to note about files is that we should be careful not to
associate more than one VHDL file object with a single physical file in the
host file system. While the language does not expressly prohibit multiple
associations, it does not specify what happens when we do several reads or
writes to the same physical file through different VHDL file objects. Hence the
results may be unpredictable and may vary from one host to another.
page 518:
-
note that files of the types we have described store the data in some binary
representation. The format is dependent on the host computer system and on the
simulator being used. This fact raises the issue of portability
-
There is no guarantee that it can be read on a different host computer, even
using the same simulator retargeted for that host, nor that it can be read on
any host using a different simulator.
-
If we do need to transfer files between systems, we can use text files as the
interchange medium.
-
The predefined package textio in the library
std provides a number of useful types and operations for reading and writing
text files, that is, files of character strings.
page 521:
- Input and output operations using textio are based on dynamic strings,
accessed using pointers of the type line, declared in the package. We use the
readline operation to read a complete line of text from an input file. It
creates a string object in the host computer’s memory and returns a pointer to
the string. We then use various versions of the read operation to extract
values of different types from the string. When we need to write text, we first
use various versions of the write operation to form a string object in memory,
then pass the string to the writeline operation via its pointer. The operation
writes the complete line of text to the output file and resets the pointer to
point to an empty string. If the pointer passed to writeline is null, the
operation writes a blank line to the output file.
page 522:
- The trap to be aware of is that if we copy the pointer to a line, using
assignment of one value of type line to another, we may end up with dangling
pointers after doing read or write operations. The best way to avoid problems
is to avoid modifying variables of type line other than with read and write
operations.
page 567:
- EXAMPLE 18.2 Monitoring states in an embedded state machine
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 | library ieee; use ieee.std_logic_1164.all;
entity control is
port ( clk, reset : in std_ulogic; ... );
end entity control;
architecture fsm of control is
subtype state_type is std_ulogic_vector(3 downto 0);
constant idle : state_type := "0000";
constant pending1 : state_type := "0001";
...
signal current_state, next_state : state_type;
begin
state_reg : process (clk) is ...
fsm_logic : process (all) is ...
end architecture fsm;
--The entity and architecture for the system being designed are
library IEEE; use IEEE.std_logic_1164.all;
entity system is
port ( clk, reset : in std_ulogic; ... );
end entity system;
architecture rtl of system is
component control is
port ( clk, reset : in std_ulogic; ... );
end component control;
begin
control_unit : component control
port map ( clk => clk, reset => reset, ... );
...
end architecture rtl;
--We can define a test bench entity and architecture that traces the sequence of
--states in the control unit, issuing a report message for each:
entity state_monitor is
end entity state_monitor;
architecture tracing of state_monitor is
alias fsm_clk is
<<signal .tb.system_duv.control_unit.clk : std_ulogic>>;
alias fsm_state is
<<signal .tb.system_duv.control_unit.current_state : std_ulogic_vector(3 downto 0)>>;
begin
monitor : process (fsm_clk) is
begin
if falling_edge(fsm_clk) then
report to_string(now) & ": " & to_string(fsm_state);
end if;
end process monitor;
end architecture tracing;
--we write the top-level entity and architecture as
library IEEE; use IEEE.std_logic_1164.all;
entity tb is
end entity tb;
architecture monitoring of tb is
signal system_clk, system_reset : std_ulogic;
...
begin
... -- clock and reset generation
system_duv : entity work.system(rtl)
port map ( clk => system_clk, reset => system_reset, ... );
state_monitor : entity work.state_monitor(tracing);
end architecture monitoring;
|
page 587:
- If we include the keyword shared in a variable declaration, the variables
defined are called shared variables and can be accessed by more than one
process. We can only declare shared variables in the places in a model where we
cannot declare normal variables, namely, in entity declarations, architecture
bodies, block statements (see Chapter 23), generate statements, and packages
that are not local to processes or subprograms.
page 588:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | type shared_counter is protected
procedure reset;
procedure increment ( by : integer := 1 );
impure function value return integer;
end protected shared_counter;
-- This declares a protected type for a shared counter with methods to reset the
-- counter value to zero, to increment the counter by some amount and to read the
-- value of the counter.
-- We can declare a shared variable to be of this type using a shared variable decla-
-- ration, for example:
shared variable event_counter : shared_counter;
-- A process uses the name of the shared variable as a prefix to a method name
-- to identify the shared variable on which the method is invoked. For example:
event_counter.reset;
event_counter.increment (2);
assert event_counter.value > 0;
-- In each case, the process acquires exclusive access to event_counter before
-- executing the body of the method. While the process is executing the method,
-- other processes that try to invoke any method on the same shared variable must
-- wait.
|
page 606:
page 607:
page 608:
page 609:
page 638:
-
there are some significant restrictions on the use of array types. They must
be indexed by an integer range, and the index bounds must be static
-
some tools limit arrays to be one-dimensional. Such tools would not allow the
matrix type
page 639:
page 642:
| with addr(1 downto 0) select
request <= request_a when "00",
request_b when "01",
request_c when "10",
request_d when "11",
'X' when others;
|
| request <= request_a when addr(1 downto 0) = "00" else
request_b when addr(1 downto 0) = "01" else
request_c when addr(1 downto 0) = "10" else
request_d when addr(1 downto 0) = "11" else
'X';
|
- However, in a conditional signal assignment, the conditions need not be
mutually exclusive, so the synthesis tool would infer a priority-encoded chain
of multiplexers to conform with the language semantics. This structure would be
slower than a simple multiplexer. The tool may be able to optimize the
hardware, but it is safer to use a selected signal
assignment to imply a multiplexer function if that is our design intent.
page 654:
- The preferred [state machine] style is to separate
the implementation into two processes, one describing the combinational logic
that calculates the next state and output values, and the other being a
register that stores the state.
page 661:
-
Different synthesis tools support different attributes to specify different
aspects of hardware inference and different aspects of target technologies.
This is possibly an aspect in which tools most widely diverge,
-
Synthesis tools usually include similar packages for their
implementation-defined attributes. The standard package is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | package RTL_ATTRIBUTES is
attribute KEEP : boolean;
attribute CREATE_HIERARCHY : boolean;
attribute DISSOLVE_HIERARCHY : boolean;
attribute SYNC_SET_RESET : boolean;
attribute ASYNC_SET_RESET : boolean;
attribute ONE_HOT : boolean;
attribute ONE_COLD : boolean;
attribute FSM_STATE : string;
attribute FSM_COMPLETE : boolean;
attribute BUFFERED : string;
attribute INFER_MUX : boolean;
attribute IMPLEMENTATION : string;
attribute RETURN_PORT_NAME : string;
attribute ENUM_ENCODING : string;
attribute ROM_BLOCK : string;
attribute RAM_BLOCK : string;
attribute LOGIC_BLOCK : string;
attribute GATED_CLOCK : boolean;
attribute COMBINATIONAL : boolean;
end package RTL_ATTRIBUTES;
|
page 668:
| -- rtl_synthesis off
-- rtl_synthesis on
|
- normally use the metacomments to exclude from synthesis parts of the model
that are only intended for simulation. Examples are processes that check timing
or that use file input/output for instrumentation.
page 673:
page 675:
page 676:
page 677:
page 707:
page 711: