Hardware Description

Languages : VHDL

David Nguyen, Duy-Ky Nguyen, PhD

1. Introduction

VHDL was first created by DOD contractors IBM, Texas Intruments and Intermetrics to document a large discrete digital project in 1980. Verilog was created by an engineer named Phil Moorby at a CAE company called Gateway Design Automation to verify a large discrete digital projects in 1981.

VHDL is a Pascal/Ada-like language (case insensitive) while Verilog is C-like language (case sensitive).

VHDL is more programming language than Verilog as it has many data types for signal and variable. Verilog is better to describe hardware and 2 basic data types, 1 for combinatory digital (memory-less like gate, decoder, mux, ...) and 1 for sequential digital (memory like flip-flop). It has only signal, not variable (signal is also variable, anyway).

A VHDL Quick Reference CHM file

Remark

There could been a confusing mistake published in HDL books saying the order of sequential statements are important. The reason could be the authors may be confused between regular meaning of sequential with one after another and its digital meaning with clock action.

The VHDL is below where keywords are in bold.


library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all; -- counter, adder (-- comment)

entity Ent_Name is port (
	I : in std_logic;
	O : out std_logic_vector (3 downto 0);	-- vector
	IO : inout std_logic
); end;	-- or end Ent_Name;

architecture Ent_Name_a of Ent_Name is	-- optionally: architecture Ent_Name with the same name
	-- component declarations for external entities
	-- signals declared after architecture being as its scope
begin
	-- concurrent assignments (with <= only)

	-- sequential assignments (process)

end;	-- or end Ent_Name_a; or end Ent_Name; if Ent_Name used after architecture (no extra _a)

Concurrent assignments are single statements while sequential assignments are block of statements, so the latter should be briefed here.

process
-- variable declarations
-- function definition
-- procedure definition
begin
-- statements
end process;
Remark
Remark
. . .
signal data: std_logic_vector(15 downto 0);
signal bit_cnt: std_logic_vector(3 downto 0);
signal ser_out: std_logic;
. . .
--ser_out <= data(bit_cnt);	-- WRONG, index within (...) must be integer
ser_out <= data(conv_integer(bit_cnt));	-- OK after conversion
. . .

2. Combinatory Digital

2.1. Gates

INV O <= not I;
AND2 O <= I0 and I1;
AND2B1 O <= (not I0) and I1;
OR2 O <= I0 or I1;
NAND2 O <= not (I0 and I1);
XOR O <= I0 xor I1;
BUFE O <= I when E='1' else 'Z'; -- conditional statement

In Verilog, combinatory digital uses `=` while sequential uses <=.


2.2. Decoder

Below is an implementation for D2_4E (2-bit select, 4-bit output with Enable).

If we have vector interfaces, say S_2x and O_4x, then

O_4x <=    "0000" when E=`0`
	Else "0001" when S_2x="00"
	Else "0010" when S_2x="01"
	Else "0100" when S_2x="10"
	Else "1000";

We have used single quote for bit, like `0` and double quote for multi-bit (vector) like "00";

But, if we have to deal with bits, then we have to include extra signal declaration outside the body, ie before the keyword begin, and use the vector implementation above.

Signal S_2x : std_logic_vector (1 downto 0);
Signal D_4x : std_logic_vector (3 downto 0);

Begin
S_2x <= (S1, S0);	-- aggregate
D3 <= D_4x(3);	D2 <= D_4x(2);	D1 <= D_4x(1);	D0 <= D_4x(0);
O_4x <=    "0000" when E=`0`
	Else "0001" when S_2x="00"
	Else "0010" when S_2x="01"
	Else "0100" when S_2x="10"
	Else "1000";

We can use concatenation operator & to build a vector from bits, like (S1 & S0), instead of (S1, S0).


2.3. Mux

Below is an implementation for M4_1E (2-bit select, 4-bit input with Enable)

O <= 	 'Z' 	 when E='0'	-- tri-state
	else D0 when S_2x="00"
	else D1 when S_2xi="01"
	else D2 when S_2x="10"
	else D3;

If we have bitwise select, we use the same method for decoder above.


2.4. Adder

For 4-bit adder, we have

O_4x <= A_4x + B_4x;

Don`t forget to add use ieee.std_logic_unsigned.all;

Again, we have to add extra vector signals for bit-wise requirement as in decoder.


3. Sequential Digital

3.1. Flip-Flop

For the simplest D-FF, FD in Xilinx, we have

Process ( C ) Begin
	If rising_edge ( C ) then
		Q <= D;
	End If;
End Process;

Or for falling edge trigger, FD_1, we have

Process ( C ) Begin
	If falling_edge ( C ) then
		Q <= D;
	End If;
End Process;

If async clr required, FDC, we have

Process ( C, CLR ) Begin
	If CLR=`1` then
		Q <= `0`;
	ElsIf rising_edge ( C ) then
		Q <= D;
	End If;
End Process;

If clk enable required, FDCE, we have

Process ( C, CLR ) Begin
	If CLR=`1` then
		Q <= `0`;
	ElsIf rising_edge ( C ) and E=`1` then
		Q <= D;
	End If;
End Process;

Note that Process has a sensitive list that trigger its action, like ( C ) or ( C, CLR) above;


3.2. Shifter

For a 8-bit left shifter S with 1-bit input I clocked by C, we have

Process ( C ) Begin
	If rising_edge ( C ) then
		O <= S(7 downto 1) & I;	-- use concat op &
	End If;
End Process;

3.3. Counter

If we need a 10-counter, then

Process ( C, RST ) Begin
	If RST=`1`
		Q <= "0000";
	ElsIf rising_edge ( C ) then
		if (Q=9) then
			Q <= "0000"
		Else
			Q <= Q + 1;
		End If;
	End If;
End Process;

4. Language

VHDL has many variable and data types even though only signal and std_logic is really required for synthesis. It follows closely Pascal/Ada style, like variable before colon `:` and type, the word is, index with ( to/downto ).

Signal sig_bit : std_logic;				-- 1-bit signal
Signal sig_vec  : std_logic_vector (7 downto 0);	-- 8-bit signal
Signal uses `<=` for both sequential and concurrent assignments, like
Sig_a <= sig_b;

For simulation, we must also need variable, like

Variable var_bit: std_logic;
Variable var_vec : std_logic_vector(7 downto 0);
Variable uses `:=` for assignment
Var_a := var_b;

Std_logic has value within single quote as `0`, `1` and `Z` for tri-state.

A 3-bit std_logic_vector has value in double quote like "000" where number of digits displayed must be equal to the vector size.

>

For vector with size of 4-bit multiple, say 8-bit, hex format can be used like x"00" instead of "00000000".

VHDL also offers a short cut using aggregate like (otheres => `0`) instead of using, say 18 0`s like "000000000000000000" for 18-bit vector. There`s no hex format for `Z` value, so aggregate is used instead, say (otheres => `Z`) instead of ZZZZZZZZZZZZZZZZ with 16 Z`s for 16-bit vector.

For a zero/one-dominant vector which has no 4-bit multiple, hex format above cannot be used, aggregate should be used for clarity, say (1|7 => '1', others '0') for "0 1000 0010", or (1|7 => '0', others '1') for "1 0111 1101".

An integer numbers may be expressed either in decimal or in a base between 2 (binary) and 16 (hexadecimal), including '_' for clarity.

	196		2#1100_0100#	16#c4#		4#301#e1		-- all the same for decimal 196

4.1. Structure

VHDL object has 3 parts

  1. Library;
  2. Declaration: entity header with IO list;
  3. Implementation: architecture with signal and variable declarations, with concurrent and sequential assignments (within process);

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all; -- counter, adder (-- comment)

Entity Ent_Name is port (
	I : in std_logic;
	O : out std_logic_vector (3 downto 0);	-- vector
	IO : inout std_logic
); end;	-- or end Ent_Name;

architecture Ent_Name_a of Ent_Name is	// optionally: architecture Ent_Name with the same name
	-- component declarations for external entities
	-- signals declared after architecture being as its scope
Begin
	-- concurrent assignments

	process (sensitive_list)
	-- variables declared after process being as its scope
	Begin - process
		-- sequential assignments
	end process;

end;	-- or end Ent_Name_a; or end Ent_Name; if Ent_Name used after architecture (no extra _a)
Remarks

4.2. Relational Operators: =(equal) /=(unequal) < (less than) > (greater than)

Relational operators result in Boolean type having 2 values TRUE and FALSE.

STD_LOGIC signal does not have Boolean value, so it must use relational operator to get Boolean.

If a signal sel has value 1 ('1' actually), one may have an illegal code below

if (a_bit) then	-- WRONG!
. . .
end if;
It must be
if (a_bit='1') then	-- RIGHT
. . .
end if;
Remark

4.3. Logical Operators: not and or nand nor xor

The 3 operators not, and or are both used for Boolean logical (with relational operators) and bit-wise logical (digital circuitry)

X_bool <= (a = b) and not (c = d);	-- boolean

X_4x <= A_4x and not B_8x(3 downto 0);	-- bit-wise

4.4. Concatenation Operator &

Concatenation is used to make a vector from bits and/or from another vector.

X_4x <= x_3 & x_2 & x_1 & x_0;		-- combine 4 bits into 4-bit vector

or

X_4x <= x_3 & x_vec(2 downto 0);		-- OK to combine 1 bit and 1 vector !!!

4.5. Aggregate Operator ( , )

Aggregate combines many same-sized values into a composite value.

X_4x <= (x_3, x_2, x_1, x_0);		-- combine 4 bits into 4-bit vector

But it`s illegal to combine mix of bits and vector

X_4x <= (x_3, x_vec(2 downto 0));		-- WRONG to combine 1 bit and 1 vector !!!

It's also used with others to fill a same STD_LOGIC value into the whole verctor, like

data <= (others => 'Z');

4.6. Conditional Statements Not used within Process

X <= 		value_1 when condition_1
	Else	value_2 when condition_2
	Else	value_3;

Remark
For process statement, we have to use IF or CASE statements, NOT conditional statement above.


4.7. IF Statements

If (condition_1) then
	Statement_1;
Elsif (condition_2) then
	Statement_2;
Else
	Statement_3;
End If;

where condition must be of boolean type using relational operators.


4.8. CASE Statements

case (expression) is
	when value_1 => statement_1;
	when value_2 => Statement_2;
	when others  => Statement_3;
End case;

4.9. WAIT Statement

Wait on sensitive_list
Wait until condition
Wait for time

4.10. PROCESS Statement

	process (sensitive_list) -- no sensitive list required if explicit wait statement is used
	-- variables declared after process being as its scope
	Begin -- process
		-- sequential assignments
		-- wait statement required if no sensitive list used after process
	end process;

A process must have wait statement to trigger action, either implicitly in sensitive list or explicitly in using wait statement.

Assume we have a D flip-flop below

Process (clock) Begin
if rising_edge(clock) then
		Sig_a <= sig_b;
	End If;
	-- implicit wait on clock
End Process;

It`s equivalent to the following

Process Begin
if rising_edge(clock) then
		Sig_a <= sig_b;
	End If;
	Wait on clock;	-- explicit wait
End Process;

In addition, any concurrent statement can be put into process statement whose sensitive list includes all inputs;

The simple concurrent assignment below

A <= b;

can be written as

Process (B) Begin
	a <= b;
End Process;

Or a mux

O <= 	 '0' when E='0'
	else D0 when S_2x="00"
	else D1 when S_2x="01"
	else D2 when S_2x="10"
	else D3;

can be written as

process (D3, D2, D1, D0, S1, S0, E)
Begin
	S_2i := (S1, S0);
	if E='0' then
		O <= '0';
	else
		case S_2i is
			when "00" => O <= D0;
			when "01" => O <= D1;
			when "10" => O <= D2;
			when "11" => O <= D3;
			when others => O <= '0';
		end case;
	End If;

end process;

Note that we cannot use conditional statement in process statement.

Remark
We will not use process statement for concurrent assignments for clarity and sake of debugging.


4.11. Function

Function must execute in zero time and return only one signal for computational purpose. It therefore does not have timing process statement. Basically, function is used to compute a complicated formula and evoked at different places.

Function func_name (argument_list) return type is
-- variable
Begin
-- function body
end;

For example,

function Decoder (E : std_logic, S  : std_logic_vector(1 downto 0)) return std_logic_vector(3 downto 0) is
Variable O_4x  : std_logic_vector (3 downto 0);
Begin
O_4x <=  "0000" when E=`0`
	Else "0001" when S_2x="00"
	Else "0010" when S_2x="01"
	Else "0100" when S_2x="10"
	Else "1000";
Return O_4x;
End;

4.12. Procedure

In contrast to function, procedure can return zero or many signals using IO list for modularization purpose. It may include timing process statements. Normally, procedure is used for repeated jobs in simulation, like cpu_write(), cpu_read(), ...

procedure proc_name (IO_list) is
-- variable
Begin
-- procedure body
end;

For example,

Type OP_CODE is (ADD, SUB, EQ, LT)

procedure ALU (
	A, B : in integer;
	OP : in OP_CODE;
	Z : out integer;
	Comp: out Boolean) is
Begin
case OP is
	when ADD => Z := A + B;
	when SUB => Z := A - B;
	when EQ => comp := A = B;
	when LT => Z := A < B;
end case;
End;

4.13. File

Values can be read or written to a file using read and write procedures, respectively.

4.13.1. File-Type Declaration

The syntax of a file-type declaration is:

type file-type is file of type-name;

Remark : TEXT is pre-defined file type.

For example

type RAM_F is file of std_logic_vector(7 downto 0);

4.13.2. File Declaration

The syntax of a file declaration is:

file file-handle: file-type [open mode] is file-name;

where mode could be WRITE_MODE or default READ_MORE, ie. not specified.

For examples of READ_MODE files

file VEC_FH: TEXT is " vectors.txt"
file RAM_FH: RAM_F is " ram_data.txt"
file RAM_FH: RAM_F open WRITE_MODE is " ram_data.txt"

4.13.3. File Operation

File operations
read(), readline(), write(), writeline(), endfile()

are defined in package TEXTIO.

type BYTE is std_logic_vector(7 downto 0);
type RAM_F is file of BYTE:

file RAM_FH_R: RAM_F is "ram_data_r.txt";
file RAM_FH_W: RAM_F is "ram_data_w.txt";

process
	variable byte_val: BYTE;
begin
    while not endfile(RAM_FH_R) loop
        read(RAM_FH_R, byte_val);
        write(RAM_FH_W, byte_val);
    end loop;
end process;



4.14. Instantiation

To instantiate an entity entity_name, we have

Entity_name_I : entity_name port map (
	Entity_if_1 => sig_1,	-- interface signal 1
	Entity_if_2 => sig_2,
	Entity_if_3 => sig_3
);

4.15. Simulation Memory Model

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

Entity spram_5x8 is
	port (
	addr: IN std_logic_VECTOR(4 downto 0);
	din: IN std_logic_VECTOR(7 downto 0);
	we: IN std_logic;
	clk: IN std_logic;
	en: IN std_logic;
	dout: OUT std_logic_VECTOR(7 downto 0));
END spram_5x8;

architecture spram_5x8_a of spram_5x8 is

type RAM is array (0 to 31) of std_logic_vector(7 downto 0);
signal	MW  : std_logic_vector(7 downto 0);

Begin

process (clk)
Variable RAMa : RAM;

Variable aa : integer := 0;
Begin
	aa := conv_integer(addr);	-- Must convert to integer for RAM index
	if rising_edge(clk) and en='1' then
		if (we='1') then
			RAMa(aa) := din;
			MW <= din;
		End If;
		dout <= RAMa(aa);
	End If;
end process;

end spram_5x8_a;

Remark
We have to use an built-in function conv_integer() to index RAM address.


4.16. Test Bench

Below is a complete VHDL code for a counter

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

Entity cntr is port (	-- uses extra signal for output
    reset_n, clk: in std_logic;
	q : out std_logic_vector (3 downto 0)
);
end cntr;

architecture cntr_a of cntr is

signal	cnt : std_logic_vector (3 downto 0);

Begin

process(reset_n, clk) Begin
	if (reset_n = '0') then	-- active-low reset
		cnt <= "0000";
	elsif rising_edge(clk) then
		if cnt = 9 then
			cnt <= "0000";
		else
			cnt <= cnt + 1;
		End If;
	End If;

	q <= cnt;

end process;

end cntr_a;

Remark

The counter above can be implemented using extra variable within process, instead of signal. Note that we have to use := for variable, instead of <= for signal.

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

Entity cntr is port (	-- uses extra variable for output
    reset_n, clk: in std_logic;
	q : out std_logic_vector (3 downto 0)
);
end cntr;

architecture cntr_a of cntr is

Begin

process(reset_n, clk)
variable	cnt : std_logic_vector (3 downto 0);
Begin
	if (reset_n = '0') then	-- active-low reset
		cnt := "0000";
	elsif rising_edge(clk) then
		if cnt = 9 then
			cnt := "0000";
		else
			cnt := cnt + 1;
		End If;
	End If;

	q <= cnt;

end process;

end cntr_a;

Inout requires no extra signal/variable


library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

Entity cntr is port (	-- inout requires no extra variable for output
    reset_n, clk: in std_logic;
	q : inout std_logic_vector (3 downto 0)
);
end cntr;

architecture cntr_a of cntr is


Begin

process(reset_n, clk)
Begin
	if (reset_n = '0') then	-- active-low reset
		q <= "0000";
	elsif rising_edge(clk) then
		if q = 9 then
			q <= "0000";
		else
			q <= cnt + 1;
		End If;
	End If;

end process;

end cntr_a;

Below is test bench for counter above


library ieee;
use ieee.std_logic_1164.all;

Entity cntr_t is
end cntr_t;

architecture cntr_t_a of cntr_t is

component cntr port (
	clk: in std_logic;
	reset_n: in std_logic;
	q : out std_logic_vector (3 downto 0));
end component;

Signalclk: std_logic;
Signalreset_n: std_logic;
Signalq : std_logic_vector (3 downto 0);

Begin

cntr_0 : cntr port map (
	clk => clk,
	reset_n => reset_n,
	q => q);

reset_n <= '0', '1' after 200 ns;	-- generate reset

process Begin	-- generate clk
    clk <= '0', '1' after 50 ns;
    wait for 100 ns;
end process;

end cntr_t_a;