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
- In this note, the term sequential has the meaning of sequential digital circuitry, like flip-flop; where sequential assignment is executed at clock edge. It`s totally different from the regular meaning used in SW for sequential statements where one statement is executed after another.
- HDL has another term concurrent may cause some misunderstanding. In HDL, concurrent assignment is associated with combinatory digital circuitry, like gate, and is executed immediately, ie not waiting for clock.
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
- Variables use (:=) assignment while Signals use (<=);
- The scope of variables are within only the process they are declared, even simulator cannot see them!
- All processes are started to execute at the very same time.
- BIT and BIT_VECTOR types takes values: '0' and '1'.
- STD_LOGIC and STD_LOGIC_VECTOR types takes values: '0', '1' 'Z', 'X', and some more like 'L', 'H', 'U', 'W', '-'.
- BIT and STD_LOGIC are CHARACRTER, not NUMBER. So their vectors are STRING, not number either.
- VHDL does require index be INTEGER, so STD_LOGIC_VECTOR must be converted to INTEGER for indexing. There will be ovewhwelming and disturbing number of warning about this conversion as INTEGER does bot have values like 'Z', 'X' in STD_LOGIC.
- So VHDL deals with STD_LOGIC as a CHARACTER, and STD_LOGIC_VECTOR as a STRING, rather than a digital number;
- We will use only STD_LOGIC and its vector in this note as it represents real HW signals. The term "bit" used in this note is a real HW bit signal, not VHDL BIT type.
. . . 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 signalSignal 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
- Library;
- Declaration: entity header with IO list;
- 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
- Concurrent assignments must be within architecture body with signal declarations, probably with component declarations for external entities used for implementation, if so required;
- Sequential assignments must be within process body with variable declarations;
- Out is write-only signal on LHS (Left Hand Side). It cannot be read (read protect), ie on RHS. This is a very serious mistake as in the real world of digital circuitry, there is no such read-protect, but only write protect read-only memory (ROM).
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
- For STD_LOGIC, it must be if (a_bit='1'), but NOT if (a_bit=1);
- For STD_LOGIC_VECTOR, it's OK to use both string or integer, like if (a_vector=x"f"), OR if (a_vec=15)
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 operationsread(), 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
- We have to use extra signal cnt because q, as an output, is write-only, and cannot be read in RHS (!!!) like in q <= q + 1. In reality, digital circuitry has read-only device, not write-only, for data protection. In addition, both read and write do require control signals, like chip select (chip enable) and read/write enable. So, VHDL creators certainly know well on programming language, but not enough on digital circuitry. This is a very unrealistic feature of VHDL ! That could be a reason why VHDL had to struggle a lot to be a language of digital design, while Verilog had no problem.
- However, we can rid of using extra signal by delaring Q as inout, but it`s also unrealistic as inout implies bidirection with tri-state.
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;