designates my notes. / designates important.
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.
--
or /* */
46E5 1E+12 19e00
1.234E09 98.6E+21 34.0e-08
"A string in a string: ""A string"". "
"If a string will not fit on one line, "
& "then we can break it into parts on separate lines."
package int_types is
type small_int is range 0 to 255;
end package int_types;
use work.int_types.all;
entity small_adder is
port (a, b : in small_int;
s : out small_int
);
end entity small_adder;
maximum(3, 20) = 20
minimum(3, 20) = 3
'1' ?= 'H' = '1'
'0' ?= 'H' = '0'
'1' ?/= 'H' = '0'
'0' ?/= 'H' = '1'
'Z' ?= 'H' = 'X'
'0' ?= 'U' = 'U'
'1' ?= '-' = '1'
'W' ?/= 'H' = 'X'
'0' ?/= 'U' = 'U'
‘-' ?/= '-' = '0'
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
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.
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
type sample is array (natural range <>) of integer;
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);
variable main_sample_set : sample_set(1 to 100)(1 to 20);
variable bakers_samples : dozen_samples(open)(0 to 9);
constant default_sample_pair : sample_set
:= (1 => (39, 25, 6, -4, 5),
2 => (9, 52, 100, 0, -1),
3 => (0, 0, 0, 0, 0)
);
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;
constant midday : time_stamp := (0, 0, 12);
constant midday : time_stamp := (hours => 12, minutes => 0, seconds => 0);
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.
next_state_logic : process (all) is
begin
...
end process next_state;
PC_incr : next_PC <= PC + 4 after 5 ns;
PC_incr : process is
begin
next_PC <= PC + 4 after 5 ns;
wait on PC;
end process PC_incr;
reset_gen : reset <= '1', '0' after 200 ns when extended_reset else
'1', '0' after 50 ns;
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;
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;
check : assert not (s and r)
report "Incorrect use of S_R_flip_flop: " & "s and r both '1'";
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;
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;
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;
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;
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;
new_temperature := limit (current_temperature + increment, 10, 100);
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.
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;
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 inc_en = '1' then
inc_reg <= inc_reg + 1;
end if;
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";
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
A4 <= to_ufixed(6.5, 3, -3); -- pass indices
A4 <= to_ufixed(6.5, A4); -- sized by A4
Conversion Function Summary Charts
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;
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.
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;
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 ( ... );
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);
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 );
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;
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;
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;
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);
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 );
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 );
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;
count := new natural;
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);
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" );
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;
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.
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;
We achieve mutual exclusion for a shared variable by declaring the variable to be of a protected type.
A simple example of a protected type declaration is
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.
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
synthesis tool does not attempt to interpret the strength information associated with the standard logic value.
‘Z’ is still acceptable in designing a tristate buffer.
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';
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:
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;
-- rtl_synthesis off
-- rtl_synthesis on