ECE385 DIGITAL SYSTEMS LABORATORY Introduction to VHDL What is VHDL: VHSIC Hardware Description Language VHSIC - Very High Speed Integrated Circuits Follows the structure of ADA programming Language Originally intended as a Simulation Language for very large systems Very Strongly Typed Language, for example, bit vector “0011” and integer ‘3’ are not easily interchangeable. VHDL is not case sensitive. Uses 9 Signal Values (IEEE standard): A Signal Value must be enclosed in single quotes ‘0’ -- Forcing 0 ‘1’ -- Forcing 1 ‘X’ -- Forcing Unknown ‘-’ -- Don’t Care ‘Z’ -- High Impedance ‘U’ -- Uninitialized ‘L’ -- Weak 0 ‘H’ -- Weak 1 Data Objects: VHDL provides different data objects, such as, signal, variable, and constant. Signals can be thought of as representing wires in the circuit. A signal holds the current and future values of an object. Declaring signals: signal <signal_name>: <signal_type> signal <signal_name>: std_logic; signal count: std_logic; signal <signal_name>: std_logic_vector(<upper_bound> downto <lower_bound>); signal vector_A: std_logic_vector(7 downto 0); Variables can be assigned a single value of a specific data type. Variables are used for computations within processes, just like in conventional programming languages. Declaring variables: variable <variable_name>: <variable_type> variable var1: std_logic; variable var2: std_logic_vector(7 downto 0); variable var3: integer range 0 to 8; Constants can be any valid data type. Constants are only allowed to be declared and initialized at the beginning of the simulation. Declaring constants: constant <constant_name>: <constant_type> constant max: integer:= 25; Difference between signals and variables: Signals can be assigned different values at different points in time so a signal can have multiple values. Signals are scheduled to receive certain values at certain points in time by the simulator, and thus signal objects must maintain a history of values. Variables, on the other hand, can only be assigned one value at any point in time. Variables are assigned value during the execution of the assignment statement. Also, you can trace signals in simulation waveforms but not variables. Data Types: You will generally be using signals and variables of type std_logic and std_logic_vector, but VHDL also has other standard data types that you can use, such as, integer, boolean etc. Single bit values are enclosed in single quotes and bit vectors are enclosed in double quotes. The data types are defined in STANDARD package libraries provided according to the IEEE standard system. Operators: Operators can be used in expressions involving signals, variables, or constant object types. Here are some of the useful operators: Logical: not, and, or, nand, nor, xor Relational: =, /= (inequality), <, <=, >, >= Addition: +, Concatenation: & (used to combine bits) Shift: sll (logical left shift), srl, sla (arithmetic left shift), sra Note: VHDL also has some other operators that are not mentioned here since all operators are not supported by all synthesis tools. Older versions of VHDL may not include some of the operators. For example, xnor is not supported in VHDL’87, but is supported in VHDL’93. Shift operators are also only supported in VHDL’93. Converting Between Data Types: CONV_STD_LOGIC_VECTOR(integer, bits) – Converts an integer to a standard logic vector. Example: CONV_STD_LOGIC_VECTOR(2, 3) will produce a standard logic vector of “010”. CONV_INTEGER(std_logic_vector) – Converts a standard logic vector to an integer. Example: CONV_INTEGER(“010”) will produce an integer value of 2. Entities and Architectures: Design Entity Entity is the primary design unit in VHDL. Multiple entities may be combined to form a larger design. An entity-architecture pair provides design entity description. Entity declaration provides the external interface to the design entity. It contains pinout description, interface description, input-output port definitions etc. Architecture describes the behavior of an entity or its structure (gates, wires, etc.) using VHDL constructs. Example: -- Always include necessary Libraries library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; -- Entity declaration. -- x and y are the inputs and z is the output of the entity called ‘Example’ -entity Example is Port ( x : in std_logic; y : in std_logic; z : out std_logic); end Example; -- Architecture definition. -- The behavior of the entity, Behavioral (can be any name), describes -- the function of the design entity. -architecture Behavioral of Example is begin z <= x or y; -- output z will be the logical or of x and y end Behavioral; Concurrency in VHDL: Concurrent Signal Assignments (CSA) All statements in a VHDL description are executed concurrently unless specified within a process. Concurrency is useful in describing combinational logic circuits. A concurrent statement is evaluated when any of its arguments change its value. A process executes only on specified triggers A process declaration includes a sensitivity list. A process executes only when one of the arguments in the sensitivity list changes. Processes are useful in describing sequential circuits and state transition diagrams. Signals and Variables: Signal assignments take effect during the next simulation cycle and after exiting a process if put in a process construct. If it is desired that the values change immediately, then variables should be used. Signals are declared before ‘begin’ in architecture definition and variables are declared before ‘begin’ in process definition. Example: Using Signals and Variables library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity sig_var_example is Port ( a : in std_logic; b : in std_logic; c : in std_logic; out_sig : out std_logic; out_var : out std_logic); end sig_var_example; architecture Behavioral of sig_var_example is signal sig1, sig2 : std_logic; -- Signals are declared here begin sig_proc: process (a, b, c) is begin sig1 <= b or c; sig2 <= sig1 and a; out_sig <= sig1 nand sig2; end process; -- Output based on signals var_proc: process (a, b, c) is variable var1, var2 : std_logic; -- Notice where variables are declared. begin var1 := b or c; -- Notice the assignment operator var2 := var1 and a; out_var <= var1 nand var2; -- Output based on variables end process; end Behavioral; In this case, the values of sig1 and sig2 will not change until the next simulation cycle after the process exits. out_sig will be computed using the old values of sig1 and sig2. out_var will be calculated from the current values of a, b, and c since var1 and var2 will reflect the results immediately (rather than during the next simulation cycle). Behavioral Simulation: Notice the difference between out_sig and out_var If-Then-Else and If-Then-Elsif Statements: An if statement executes a block of sequential statements upon matching certain condition(s). It can also include an else component or one or more elsif (not ‘elseif’ or ‘else if’, no ‘e’ in else) components. If-then-elsif construct forces a priority order in the logic. I.e. the first statement will be evaluated first and then the following statements in order. If statements are only allowed within a process. The statements are executed sequentially if a conditional match is detected. To avoid inferred latches, all outputs should be assigned values on all execution paths. Keep common statements outside of the if-then-else or if-then-elsif statements. Example: Inference library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity if_example is Port ( x : in std_logic; y : in std_logic; z : in std_logic; sel : in std_logic; w : out std_logic); end if_example; architecture Behavioral of if_example is begin example inference: process(x, y, z, sel) --Create a process variable s1, s2: std_logic; begin if (sel = '1') then s1 := x and y; s2 := s1 xor z; w <= s1 and s2; --Since w gets a value only conditionally, -- a latch is inferred. end if; end process; end Behavioral; To avoid the inferred latch, assign a default value to w outside the if-then statement. For example, you can add "w <= '0';" before the if-then statement. You can also add the same statement in the else part using an if-then-else statement. Case Statement: Equivalent to nested set of if-then-elsif constructs but produces less logic since it does not force priority order. It identifies mutually exclusive blocks of code. Since all possible values of select inputs must be covered, use the others clause. Case statements have to be inside a process. Case statements can be used to describe multiplexors. Example: Case Statement library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity case_example is Port ( a : in std_logic; b : in std_logic; sel : in std_logic; c : out std_logic); end case_example; architecture Behavioral of case_example is begin case_process: process(a, b, sel) is begin case sel is when '0' => c <= a; when '1' => c <= b; when others => c <= '0'; end case; --Use the ‘others’ clause --Don’t forget the ‘end case’ end process; end Behavioral; When-Else and Select Statements: A When-else statement is similar to an if-then-else statement. It forces a priority structure and conditions are evaluated sequentially. A Select statement is similar to a case Statement. It requires all possible input combinations to be covered by mutually exclusive conditions. Since it does not force a priority structure, the synthesis tools can produce better-optimized logic than with whenelse statements. Unlike if-then-else and case statements, when-else and select statements do not have to be inside a process. See the examples below with when-else and select statements. Example: 4-to-1 Multiplexer Din sel 4 2 ENTITY mux ARCHITECTURE Design 1: entity mux is port (sel: in std_logic_vector(1 downto 0); Din: in std_logic_vector (3 downto 0); Dout: out std_logic); end entity; Dout architecture my_mux_behavior of mux is begin Dout <= Din(3) when sel=“11” else -- first evaluate this Din(2) when sel=“10” else -- next evaluate this Din(1) when sel=“01” else -- then evaluate this Din(0) when sel=“00” else -- then evaluate this ‘X’; -- if all fails then X end my_mux_behavior; Design 2: (Better than Design 1 since produces less logic) entity mux is port (sel: in std_logic_vector(1 downto 0); Din: in std_logic_vector (3 downto 0); Dout: out std_logic); end entity; architecture my_mux_behavior of mux is begin with sel select -- there is no specific order under which conditions are evaluated Dout <= Din(3) when “11”, Din(2) when “10”, Din(1) when “01”, Din(0) when “00”, ‘X’ when others; --“default case” must be included end my_mux_behavior; Designing Latches and Flip-Flops: Flip-Flops and other clocked devices can only be synthesized inside a process. Example: Positive-Edge Triggered D-Flip-Flop with Asynchronous Reset library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity d_ff is Port (clk : in std_logic; reset : in std_logic; d : in std_logic; q : out std_logic); end d_ff; architecture Behavioral of d_ff is begin out_Q: process(clk, reset, d) is --Create a process begin if (reset = '1') then q <= '0'; elsif (rising_edge(clk)) then --Check for rising_edge of clock q <= d; end if; end process; end Behavioral; Creating Hierarchical Design Using Components: As mentioned earlier, you can create larger designs using entities as the components that make up the design. One entity can instantiate another entities as components of it. One entity can use different entities as components as well as instantiate multiple copies of the same entity. Example: Creating a 4-bit adder using a full-adder component Design Entity Full-Adder entity full_adder is port (x, y, z : in std_logic; s, c : out std_logic); end entity; architecture Behavioral of full_adder is begin s <= x xor y xor z; c <= (x and y) or (y and z) or (x and z); end Behavioral; Use component full_adder to create a 4-bit adder: ADDER4 A3 B3 c_out x y FA3 c s z S3 A2 B2 A1 B1 x y c2 cFA2 z s x y c1 cFA1 z s S2 A0 B0 x y c0 FA0 c s z S1 c_in S0 Design Entity ADDER4 that uses multiple full-adder components entity ADDER4 is port (A,B : in std_logic_vector (3 downto 0); S : out std_logic_vector (3 downto 0); c_in : in std_logic; c_out : out std_logic); end entity; architecture structural of ADDER4 is -- omit “is” for older simulators --Declare component that will be used in architecture definition component full_adder is port(x,y,z: in std_logic; s,c: out std_logic); -- reproduce the entity description end component full_adder; -- omit name “full_adder” for older simulators signal c0,c1,c2: std_logic; -- internal carries in the the 4-bit adder begin -- this illustrates how to instantiate and connect components FA0: full_adder port map(x =>A(0), y =>B(0), z =>c_in, s =>S(0), c =>c0); FA1: full_adder port map(x =>A(1), y =>B(1), z =>c0, s =>S(1), c =>c1); FA2: full_adder port map(x =>A(2), y =>B(2), z =>c1, s =>S(2), c =>c2); FA3: full_adder port map(x =>A(3), y =>B(3), z =>c2, s =>S(3), c =>c_out); end structural ADDER4; More Examples: Example: Sequential Circuit enable up_down asynch_clr clk 8-bit Up-Down Counter Q(7)Q(6) . . . . . Q(1)Q(0) entity up_down_counter is port (clk, enable, up_down : in std_logic; asynch_clr: in std_logic; Q: out std_logic_vector(7 downto 0)); end entity; architecture counter_behavior of up_dn_counter is signal count: std_logic_vector(7 downto 0); -- count is an internal signal to this process Begin process(clk, asynch_reset) -- sensitivity list begin if (asynch_reset=‘1’) then count <= “00000000”; elsif (clk’event and clk=‘1’) then -- rising edge if (enable=‘1’) then if (up_down=‘1’) then count <= count+”00000001”; else count <= count-”00000001”; end if; end if; -- ‘end if’ is not permitted here for ‘elsif’ end if; end process; Q <= count; end counter_behavior; NOTE: We cannot use “Q <= Q + 1” since Q is defined as output only. If we want to use “Q <= Q + 1” then we can declare Q as type ‘buf’ instead of ‘out’. e.g. Q: buf std_logic_vector(7 downto 0) - Notice ‘elsif’, not ‘elseif’ or ‘else if’ Notice ‘end if’, not ‘endif’ Use ‘rising_edge(clk)’ rather than ‘clk’event and clk=’1’’ for true rising_edge detection. Example: 4-Bit Shift Register D Shift_In Load Shift_En Clk 4 4-Bit Register “reg_4” 4 Data_Out Shift_Out entity reg_4 is Port (Shift_In, Load, Shift_En, Clk : in std_logic; D : in std_logic_vector(3 downto 0); Shift_Out : out std_logic; Data_Out : out std_logic_vector(3 downto 0)); end reg_4; architecture Behavioral of reg_4 is signal reg_value: std_logic_vector(3 downto 0); begin operate_reg: process (Load, Shift_En, Clk, Shift_In) begin if (rising_edge(Clk)) then if (Shift_En = '1') then reg_value <= Shift_In & reg_value(3 downto 1); -- operator “&” concatenates two bit-fields elsif (Load = '1') then reg_value <= D; else reg_value <= reg_value; end if; end if; end process; Data_Out <= reg_value; Shift_Out <= reg_value(0); end Behavioral; State Machine Design: Example: Control Unit Reset LoadA LoadB Execute Clk Control Shift_En Ld_A Ld_B entity control is Port ( Reset, LoadA, LoadB, Execute : in std_logic; Clk : in std_logic; Shift_En, Ld_A, Ld_B : out std_logic); end control; architecture Behavioral of control is --declare A, B, ..., F of type cntrl_state --User defined type “cntrl_state” has 6 symbolic values. type cntrl_state is (A, B, C, D, E, F); --declare signals state and next_state of type cntrl_state signal state, next_state : cntrl_state; begin control_reg: process (Reset, Clk, LoadA, LoadB) begin if (Reset = '1') then state <= A; elsif (rising_edge(Clk)) then state <= next_state; end if; end process; --Assign 'next_state' based on 'state' and 'Execute' get_next_state: process (Execute, state) begin case state is when A => if (Execute = '1') then next_state <= B; else next_state <= A; end if; when B => next_state <= C; when C => next_state <= D; when D => next_state <= E; when E => next_state <= F; --wait at state F until 'Execute' = 0 when F => if (Execute = '0') then next_state <= A; else next_state <= F; end if; -- “when others =>” default case is not needed here since there are -- only six values for “state” and we have exhausted them all. end case; end process; --Assign outputs based on 'state' get_cntrl_out: process (LoadA, LoadB, state) begin case state is when A => --Only load value in register(s) when in state A Ld_A <= LoadA; Ld_B <= LoadB’ Shift_En <= '0'; --No Load or Shift when in state F when F => Ld_A <= '0'; Ld_B <= '0'; Shift_En <= '0'; when others => --This is used for states B, C, D, and E Ld_A <= '0'; Ld_B <= '0'; Shift_En <= '1'; end case; end process; end Behavioral; Other Notes/Hints: A design that simulates is not guaranteed to synthesize. Simulation tools allow simulating designs that may not be physically possible to implement. It is always a good idea to assign values to signals/outputs in all possible cases. That way we can avoid inferred latches. If we do not specify the value of a signal for some input combination, then the design will have inferred latch(es) because it will try to hold the old value of the signal. Keep in mind that VHDL statements are usually executed concurrently and not sequentially. When a value is assigned to a signal, it does not take affect until the next simulation cycle. Include all inputs that can change the outputs in the sensitivity list of a process to avoid different circuit behavior from simulation and synthesis. You can manipulate when the process executes by including/excluding certain inputs from the sensitivity list, but that will only work for the simulation model. If the synthesized design is combinational, then the logic will respond to events on any inputs, not just the inputs in the sensitivity list. VHDL allows delay/wait statements, but that is only guaranteed during simulation. The synthesized design may not have similar delays. For example: x <= y after 5 ns; This is allowed, but synthesis behavior is not guaranteed. VHDL compilers generally allow while-loop statement, but synthesis tools generally don’t support it. Don’t use it in a design that needs to be synthesized. Do not specify initial values in signal declarations. Synthesis compilers ignore initial values. Initial values can be assigned explicitly under a controlling signal (e.g. reset). Assign ‘don’t care’ values to signals when possible for default cases. It may allow the synthesis compiler to produce a better design. Don’t use don’t care symbols in comparisons. If you use integer type signals, specify value-ranges in the declaration as default sizes can produce a rather large design. Case statements produce less logic than if-then-else statements since if-then-else statements produce priority logic also. You cannot have a wait statement in a process unless it is the first statement in the process and the only wait statement in the process. The process will also need to have an empty sensitivity list. Do not assign value to the same signal via multiple statements that can execute simultaneously. Write your code for synthesis rather than just simulation, as you will need to synthesize your designs for all experiments involving VHDL. References: Yalamanchili, Sudhakar. Introductory VHDL – From Simulation to Synthesis. New Jersey: Prentice-Hall, 2001. Hamblen, James and Furman, Michael. Rapid Prototyping of Digital Systems – A Tutorial Approach, Second Edition. Kluwer Academic Publishers, 2001.