Translate

Wednesday, November 30, 2011

7 segment displays

    In this tutorial I will show you how to implement a BCD to 7 segment decoder. First let's see what a 7 segment display is:
    In the picture above you can see a single digit 7 segment display. As you can tell the name 7 segment comes from the fact that is has 7 LEDs forming the number 8 if they are all on. The 7 LEDs can be individually lit in order to form numbers from 0 to 9 and some letters of the alphabet.
    If you remember a LED has 2 parts: the anode witch is the positive part and the cathode witch is the negative part. If we apply a positive voltage to the anode a we put the cathode to ground (0V) then the LED will light up. In order to be able to individually light up the 7 segments and use as few pins as possible one of the sides on the LEDs must be common to all of them (either the anode or cathode) and the other is individual (7 data lines).
    The display with one anode is called common anode and the other is called common cathode. The representation for the 2:
    In these pictures an eight segment is included but it isn't wery used. It is the decimal point you see when for example you want to write a real number like 2.5. The other 7 LEDs are a through g. As you can see in the two pictures a digit 7 segment display has 8 pins (1 is the common element and 7 are the data pins witch control each LED). In the first picture is a common anode diagram. In order to make the number 8 we need to put the anode in 1 so that the circuit is active and put all the cathodes in 0. For number 1 we need the anode in 1 of course and only 2 cathodes in 0 so that the number one will appear. For the second picture the display is activated by putting the cathode in 0 and each segment is lit with a logic 1 on the specific anode pin.
    The segments are arranged as follows:
    How 7 segment displays connect to FPGAs
    FPGA dev boards usually have 4 or 6 seven segment displays. On some dev boards they are linked  to the FPGA to 7 pins for the data and one pin for the common element each like in the diagram:
    This type of connection is rare but if you have a board with 7 segment displays connected like this you will have a easier job using them in your projects.
    Another way these circuits are connected is with separate common elements (the same as in the picture above but with common data pins. These displays require a technique named multiplexing in order to use all the digits. This tutorial does not cover multiplexing so if you have a connection like this one all your digits will show the same number.
    
    You can tell from this picture that we can't use both the 7 segment displays in normal conditions because if we activate them both then the same character will be shown on them. If you have a dev board with the displays like this, you will see in a later tutorial how to send different data to every 7 segment cell.
   One last note: some boards have the common element inverted. Mine for example has a common anode display but the anodes are activated by a logic 0. The cathodes are normal (0-active). My board is a Digilent Nexys 3 so if you have it you're in luck. For other boards you have to figure it out for yourselves.

    From BCD code to 7 segment code
    BCD stands for binary coded decimal. A BCD code is a 4 bit number that ca represent the numbers 0-9 (0000, 0001, 0010, 0011, 0100, 0101, 0110, 0111, 1000, 1001). A BCD to 7 segment decoder maps each of these 10 codes to 7 bit codes (one bit for each segment) that will control the display.
    The chart for both common anode and cathode is:

    Decimal   BCD     Common anode     Common cathode
                                      gfedcba                 gfedcba

          0       0000          1000000               0111111
          1       0001          1111001               0000110
          2       0010          0100100               1011011
          3       0011          0110000               1001111
          4       0100          0011001               1100110
          5       0101          0010010               1101101
          6       0110          0000010               1111101
          7       0111          1111000               0000111
          8       1000          0000000               1111111
          9       1001          0010000               1101111

    Using this table you can make a decoder with the selected signal assignment or however you want. My example is for common anode but you can use the codes for common cathode and leave the rest of the program unchanged if you need them.
    Example for 0: I put the values from g to a so when we want to show a 0 only the g segment will be shut down (1 for common anode or 0 for common cathode).
    You can see that when we have an 8 all the segments are lit.

    The 7 segment decoder
    The decoder will have 4 inputs of type std_logic or one input of type std_logic_vector(3 downto 0). I will be using std_logic_vector. The output is a std_logic_vector(6 downto 0). The decoding will be done with the selected signal assignment statement.
    The diagram :
    For implementation use 4 slide switches as inputs and the 7 data elements as outputs. The common element usually activates itself when not declared. If not then you must declare one more output of type std_logic and give it the value 0 or 1 depending on the element you have (anode or cathode).
    The code is:
    
    As you can see for other values other than 0-9 the LEDs will not light up. If you also need to declare the anode just put the declaration in the entity and then give it the value 0 or 1 in the architecture. 
     
    

    

     
 

Monday, November 28, 2011

Sequential statements

   In order to use sequential statements we need a special concurrent structure called a process because in an architecture body sequential code is not allowed so first let's see what a process is and how it fits in the description of a circuit.
    Here's a diagram of a VHDL program with processes:
    As you can see an entire process can be thought as a concurrent statement and the code inside it is sequential. This means that 2 processes execute concurrently relative to each other and other concurrent code but the statements inside them are sequential.
    A process has the following structure:
              name: process(sensitivity list)
                   variable declarations
              begin
                   sequential statements
              end process;

    The name: part is not mandatory but it helps you find a certain process when you have lots of them in the same vhdl file, although the same thing can be achieved by using comments. Variable declarations are also optional and we will get to them later in the tutorial.
    Let's see what the sensitivity list is. Basically a process executes only when it is triggered by an event (a signal that changes value) so we need to put all the signals that make the internals of that process in the list. Let's take for example a simple 3 input xor function (r<= a xor b xor c). The process for this code must have in it's sensitivity list all 3 input signals because we want the result r to be updated every time one of the inputs changes value. If for example in the list we would put only a then the result would be updated when a changes value but not when b or c change their values. This is a very important thing to remember for when you will design complex circuits. The process for this function looks like this:

              process(a,b,c)
              begin
                   r <= a xor b xor c;
             end process;
    
    Variables are used only in processes. A variable declared in a process is local to that process so you can have more processes with the same variables in a vhdl file. A variable can have all the data types a signal has and it is not that different from signals. Let's see how to declare and use a variable:

              variable count : integer;
              ...................................
              count := count +1;
              ...................................

    So the big difference between a signal and a variable is that a variable is assigned a value with  := .
    
    Sequential statements
    1)  Signal assignments:
         This is the same as in concurrent code but you have to be careful with the order in witch you put the statements because they execute one after the other.

              signal <= value;
   
    2)  Variable assignments:

              variable := value;

    3)  The if statement:
          This is a conditional statement in witch a block of sequential code is executed if a condition is true. The structure of the if statement is:

              if (expression 1) then
                  sequential block 1
              elsif (expression 2) then
                  sequential block 2
             ...............................
             else sequential block n
             end if;

          Another way to write an if statement is:

              if (expression 1) then
                  sequential block 1
              else
                  if (expression 2) then
                      sequential block 2
                  else sequential block 3
                  end if;
              end if;

          So in an if statement the first expression is evaluated and if it is true then the code attached to it is executed. If it is not true the next statement is evaluated and so on. 
          Let's see how the multiplexer from the last tutorials can be designed using the if statement:

              process (s,i0,i1,i2,i3)
              begin
                  if (s="00") then 
                      o <=  i0;
                  elsif (s="01") then
                      o <= i1;
                  elseif (s="10") then
                      o <= i2;
                 else o <= i3;
                 end if;
              end process;

          As you can see in the sensitivity list I have included s and the 4 inputs so that even if s doesn't change, the output will be updated with each input change. In order to test the code replace the concurrent statements from the other examples with the process. The entity is the same.

    4)  The case statement:
          This is similar to the with select statement. Let's see the structure:

              case expression is
                  when value 1 =>
                      sequential block 1
                   when value 2 =>
                       sequential block 2
                   ..............................
                   when value n =>
                       sequential block n
                   when others =>
                       other sequential block
              end case;

          The case is simple: a certain block of sequential code is executed based on the value of expression. Let's see the multiplexer with the case statement:

              process (s,i0,i1,i2,i3)
              begin
                  case s is
                      when "00" =>
                          o <= i0;
                      when "01" =>
                          o <= i1;
                      when "10" =>
                          o <= i2;
                      when others =>
                          o <= i3;
                  end case;
              end process;

    5)  The for loop:
          The for loop executes a sequential block of code over and over again for a number of times. It isn't used as much as the other 2 statements so that's why for now I won't be making an example for it. The structure of o for loop is:
             
              for index in range loop
                  sequential block
              end loop;

          We will use for loops in later tutorials.    
         
          
   

Monday, November 21, 2011

Designing a simple ALU with multiplexers

    What is an ALU? It is short for Arithmetic Logic Unit and it represents a circuit found in processors that performs different arithmetic and logic functions on 2 or maybe more operands. The basic functionality of an ALU is: it receives 2 operands on a number of bits and a select_operation signal that specifies the operation to perform and then outputs the result to another circuit like a register.
    The ALU we will design is very simple and you won't find such a circuit in any processor out there but it's a good circuit to see the application of multiplexers.
    Design data:
       - two 3 bit operands
       - a 3 bit result
       - a 2 bit select_operation input (so we will be able to have 2^2 operations)
       - 4 operations to perform (and, or, sum, xor)
   
    Note: the sum function is not a complete add operation because the operands and th result are 3 bits in length although the result should be of 4 bits ("111" + "001"= "1000"). Real adders will be covered in more advanced tutorials.

    The black box of the circuit:
    The entity should look like so:

    Another important thing to note is that in order to use the + operation you need the numeric library declared:
    use IEEE.numeric_std.ALL;

    Now let's talk about how the ALU will work:
    op1 and op2 are the 2 input operands. These will go through the 4 operations specified and the results will be put in 4 signals. Those signals represent the data inputs of the multiplexer and operation is the selection signal. The output of the mux is the result. So if we have op1="1010", op2="0010" and operation="10" witch will be addition then the result will be result="1100".
    
    Let's see the internals of the ALU:

    This is the basic structure of an ALU. The circuit can be described using data flow or structural description. Because the operations we are using are simple it's better to use data flow but when designing advanced ALUs with complex operations it's better to describe it using structural description.

    The code is:

    Perhaps an explanation is needed for sum:
    unsigned(op1) and unsigned(op2) transforms the std_logic_vector type into unsigned type in order to do the + operation (that's why we imported the numeric library). After the operation is done we need it in std_logic_vector form so we use std_logic_vector(unsigned(op1)+unsigned(op2)). It is like using a signal addition of unsigned type to store the addition result and then converting it like so:
       signal addition:unsigned(2 downto 0);
       
      addition<=unsigned(op1)+unsigned(op2);
      sumsig<=std_logic_vector(addition);

    This is just one of the applications of multiplexers. As the tutorials get more complex you will see more and more situations in witch you will need these circuits.
     
    
    
   

Saturday, November 19, 2011

Concurrent statements

    Until now you have seen how to transform a truth table in concurrent signal assignments or in processes. But not every circuit can or should be described only with logic functions. That's why VHDL has 2 concurrent signal assignment statements:
       - conditional signal assignment
       - selected signal assignment

    In order to demonstrate these 2 statements we will design 2 circuits: a 4 to 1 multiplexer and a 2 to 4 decoder. Let's see first what these circuits do:
 
    4 to 1 multiplexer
    Such a circuit has 4 data inputs, 2 select inputs and 1 output:

    This is the basic I/O of any mux. The 4 inputs can be of any bit length (from 1 bit to what your design needs). The s input can also be of different bit lengths but one rule needs to be respected: number of inputs=2 to the power of s. So if you want a 2 to 1 mux you will need a 1 bit select signal. For a 4 to 1 mux the s signal needs to be 2 bits in length. The output o will receive one of the inputs based on the value of s.

       s    o
      00   i0
      01   i1
      10   i2
      11   i3
    This is the table that a multiplexer follows.

    2 to 4 decoder
    A n to 2^n decoder takes n inputs that act similarly to the s input of the mux and outputs a 2^n value based on the input value. Let's see how a decoder looks:
    The table for a decoder is:
       
       i       o
     00    0001
     01    0010
     10    0100
     11    1000
   
    The circuits works exactly as you see in this table so no further explanations are needed.

    Entity coding
    The entities for the 2 circuits should be like:
    For the mux and for the decoder:

    Conditional signal assignment
    This type of statement is not the best way to describe the 2 circuits because it is priority based but it is a good way to understand how to use it.
    The structure of the statement is:

    output <= input1 when condition1 else
                    input2 when condition2 else
                    ..........................................
                   inputn;

    As you can see the program will evaluate the first condition and if it is true the output will get the input1 value. If condition1 is false then condition2 will be evaluated and so on. If none of the conditions are true then output will get the inputn value.
    This is the reason I recommend not to use this statement to describe multiplexers and decoders/encoders. The conditional signal assignment is best suited for priority encoders and other such circuits.

    The VHDL code for the 4 to 1 4 bit multiplexer:

    The VHDL code for the 2 to 4 decoder is:

    Selected signal assignment
    This type of assignment is the best way to describe multiplexers and decoders because they are not priority based.
    The structure of the statement is:
    
    with select_signal select
           output <= value1 when select_sig_value1,
                           value2 when select_sig_value2,
                           ..............................................
                          valuen when others;

    So the output will get one of the n-1 values depending on the select_sig value. If none of the values specified are met then the output will get valuen.

    Let's the code for the mux and the decoder:


    Implementation
    For implementation the outputs will be set as LEDs for both the multiplexer and the decoder. The s and i inputs will be slide switches.
    For inputs i0-i3 you can assign constant values in code and see how they are routed to the LEDs with the values from the switches.
   

Friday, November 11, 2011

VHDL design types

    As you have seen a VHDL described circuit has 2 main parts: the entity and the architecture witch contains the functionality of the circuit. There are 3 ways to describe digital circuits in VHDL:
       - Dataflow
       - Behavioral
       - Structural

    In this tutorial I will describe the same circuit using all 3 types of description so you can see the difference between them.
 
    The designed circuit
    A very simple but useful circuit is the parity detector. To determine the parity of a binery number we need to count the '1' bits. If that number is even than it is of even parity. If it's odd than the number is of odd parity. My tutorials will describe a circuit that outputs the even parity.

    Parity detector with 3 inputs and 1 output truth table:

       A  B  C   O
       0   0   0    1
       0   0   1    0
       0   1   0    0
       0   1   1    1
       1   0   0    0
       1   0   1    1
       1   1   0    1
       1   1   1    0
    So when the number of '1's is enev the output is '1' and vice versa.
    As you can see the truth table is rather large so it needs to be minimized. Since logic function minimization is out of the scope of this tutorial I will not be presenting it but there are lots of articles on the internet about the ways to do it.
    The circuit we will describe looks like this:

    There are other ways to design the logic diagram but using XOR gates is the most simple circuit for detecting parity.
    Regardless of the way we will describe the circuit, the entity will remain the same:
    

    Dataflow description
    In dataflow we describe circuits using only concurrent statements, mainly signal assignments. 
    A signal can be seen as a wire between 2 circuits. For example in the upper diagram abSIG is a signal that connects the first XOR output with the second XOR input.A, B, C and O are signals also but they connect the entity with the FPGA pins. 
    For this circuit we can use a description with signals or we can write the whole logic function in one statement. I will demonstrate both approaches.
    
    Without signals:
    You just have to write in the architecture 
              o <= not(a xor b xor c);


    With signals:
    For this method we need to declare 2 signals like those in the diagram. They will be of type STD_LOGIC. 
    The code looks like this:


    Remember that the order of the statements is not set in stone because they take effect at the same time. 

    Behavioral description
    This type of description uses sequential code that needs to be written inside a structure named process. The process is located in the architecture body and there can be more of them in the same architecture. Processes execute concurrently with each other but in each process the code is sequential.
    Declaration of a process:
              name: process(sensitivity list)
    The name is not necessary but when you have lots of processes it helps to easily find the one you want. The reserved word process is mandatory.
    The sensitivity list is a list of signals that control the entrance in the process. That means that when a signal in the list changes value the process will execute it's code so you need to be careful to put all the necesary signals in that list.
    After this line we can declare variables to use for the process. Variables, if you recall are local to each process they are declared in and help you build complex algorithms and circuits.
    The code of the process comes next. It is enclosed in begin-end statements.
    For this example we will not be using variables. The code looks like:

    If you look at the sensitivity list you can see that every time one of the inputs changes it's value the process will run o will take a new value.
    If we use signals the code will look exactly like for dataflow but it is important to keep it exactly the same because in processes code is sequential. This means that if you decide to put the o assignment first your code will be incorrect.

    Structural description
    This type of description is mostly used for more complex projects because you can segment your code in smaller circuits and connect them together using signals, hence the word "structural". 
    For our circuit we will need to make a description for the xor gate and one for the not gate. After that we will make another description of the main circuit in witch the others are connected.
    Because this method is more complicated I will be walking you through the steps.
    First create a new project with and give it a name:

    After that set your device parameters, click next and click finish.
    We are ready to start the coding. Create an entity and name it xorGate. It will have 2 inputs (i0 and i1) and 1 output (o). After it is created write in the architecture body: o <= i0 xor i1;

    Now create another entity named inverter. It will have 1 input (i) and 1 output (o).

    These are the 2 circuits needed for the main architecture.
    Before we continue let's see a block diagram of our project:

    As you can see the main description will have 3 inputs (A, B and C) and 1 output (O). 
    Because The main circuit and the 2 gates are in different files we need a way to link them. This is done of course in the main file using components. They are declared before the architecture begins (with the signals). There will be a component for each type of circuit (in our case one for andGate and one for inverter) and the structure of the component is exactly like that of the entity with 2 differences:
       - instead of the entity keyword at the beginning we will use component
       - instead of the name of the entity at the end statement we will use component as well

    Let's create a new entity called parityDetector with the I/O specified and declare our 2 components:

    After the components we need to declare our signals. These are used to link the components together.

    Now it's time to link the components. Because there are 3 circuits in the block diagram we will be instantiating 3 components (2 xor gates and 1 inverter). 
    Each instantiation has 3 parts:
       - A label that has to be unique for each component instantiation. It is placed first and it should have a suggestive name for that circuit
       - The name of the component instantiated (the same name of the entity for the specific circuit)
       - The reserved words "port map" followed by a list of signals in parentheses. The signals are the ones specified in the main entity (either the I/O of that entity or declared signals) and need to be placed in the order that the component has it's I/O placed. For example the inverter has it's I/O signals in the order: i,o so the instantiation for it would be:
              INV: inverter port map(parity, O);
    From that piece of code we conclude that the signal parity witch is also linked to the output of an xorGate is linked to the i pin of the inverter and the main output of our circuit: O is linked to the output pin of the inverter (o).
    The code looks like this:

    So the XORG1 circuit will compute an xor operation on A and B and communicate the result to XORG2 through the abSIG signal. XOR2 calculates abSIG xor C and gives the result to the inverter using the parity signal. The inverter performs the not operation and sends the result to the main output of the circuit. The order is not important because they are concurrent statements.
    So the structural design is similar to a schematic design because we connect circuits with wires (signals). It is best used in more complex projects.
    After you do the last bit of code save the project and the parityDetector entity should become the top level module:

    Next put in the user constraints, implement and see the results. 
    For all the examples in this tutorial the inputs should be 3 slide switches and the output a LED.
    
    
    
     
    
    
    
 
 
 
 

Tuesday, November 8, 2011

Basic logic functions

    Boolean algebra is at the heart of digital electronics so it is very important to know the functions that operate on base 2 numbers or bits. The term function is used when we discuss functions on paper or in programming, but when designing digital circuits those functions are materialized in logic gates (circuits that perform the basic operations). The circuits I will present have 2 inputs and 1 output but there are variations with more than 2 inputs.

    The AND Function
    This function performs a product so for a 2 input AND gate the output will be input one times input two. The representation of an AND gate is:
    The truth table is:
    
        A  B   O
        0   0    0
        0   1    0
        1   0    0
        1   1    1

    From this table it is clear that the and function is actually a*b. The output will be 1 only when all the inputs are 1. AND is also a minimum function because the output is the minimum value of the inputs or min(A,B).

    The OR function
    OR is a maximum function because the output will have the biggest value of all the inputs. For an n input OR function, if n-1 inputs are 0 and only one is 1 then the output will be 1.
    Representation of an OR gate:
    The truth table is:
        
        A  B   O
        0   0    0
        0   1    1
        1   0    1
        1   1    1

    So we see that the output is 0 only when there isn't a 1 at the input.

    The XOR function
    XOR is short for exclusive OR The output is 1 only when an input is 1 and the other is 0. This function is very useful when detecting parity for example.
    Representation of XOR:
    The truth table is:

        A  B   O
        0   0    0
        0   1    1
        1   0    1
        1   1    0

    Inverter (NOT)
    It is a very important circuit. It takes an input and inverts it's value at the output. So 0 becomes 1 and 1 becomes 0.
    The symbol for the inverter is:
    Other functions
    There are 3 more functions to mention: nand, nor, xnor. These are exactly like and, or, xor but the output is inverted. The symbols are also almost the same. The difference is that these have a small circle at the output side. For example the NAND gate looks like this:
    If you do some project that requires one of these 3 gates but you don't have it, use the non-inverted gate plus an inverter circuit.

    Using all the gates in a project
    Let's make a circuit that takes 2 inputs: a and b. The circuit will have 8 outputs: a and b, a or b, a xor b, a nand b, a nor b, a xnor b, not a, not b. For a and b we will use 2 slide switches and for the outputs 8 LEDs.
    If you don't remember how to use Xilinx ISE you can read my tutorial about starting a project:Link.
    Let's make the entity. It should be like this:
    

    You don't have to use the same names for the entity or I/O of course. Also you can use a 2 element std_logic_vector for the inputs and an 8 element std_logic_vector for the outputs f you want.
    Now it's time to write the code. It should look like this:



    Small reminder: The order in witch you put the statements doesn't count because in the architecture all the statements are concurrent.
    Now you can put in the constraints and implement the project.

    
    
    
    
    
    
 
    

 

 

Friday, November 4, 2011

Basic VHDL elements

    Even though VHDL isn't a programming language, has some similar elements in common with the latter. Those include lexical and data type elements.

    Lexical elements
    1)  Identifiers represent names given by you to ports, signals, variables and other objects of VHDL. There are some rules for writing identifiers:
              - They contain only letters, decimal digits and underscores
              - An identifier must begin with a letter
              - The last character must not be an underscore
              - Identifiers can not contain 2 consecutive underscores
    Examples of identifiers: enable, num1, write_e
    Another important note is that VHDL is case insensitive. In translation you can write an identifier either with lower case characters, either with upper case characters. Combinations of upper and lower case is the same as well.
    Example: ENABLE, enable, EnablE are the same.

    2)  Reserved words are special language reserved words like: port, end, begin, entity, and, or, not, architecture etc. You can not use these words as identifiers.

    3)  Comments can be used to document your work. They do not influence the functionality of the program but an indispensable asset to any programmer because by documenting pieces of code you can read it very easily after a long time.
         Every comment starts with 2 dashes (--) and is line specific
    Example:  --this is a signal assignment
                    a<='0';


    Objects

    1)  Signals are the most common object used in a VHDL program. They can be seen as a wire or connection between two ports. Signals are declared after the architecture name and before the begin statement.


              architecture arch_a of arch is
                  signal a,b :  std_logic;
              begin
    
    In the architecture body you assign values to signals with <= like so:


               a<='1';
               b<=a;


    The entity ports are considered signals to. 
    

    2) Constants are objects are given a value and it does not change. Usually they are written with capital letters and are declared after the architecture with signals. Example of constant declaration:

     
              constant BITS : integer := 8;


    As you can see the constant is named BITS, is of type integer (we'll get to that later) and has the value 8.


    3) Variables are used only in sequential code (process). Their role is to describe complex algorithms in circuits. A variable is local to the process in witch it is declared.
    Example:

              process()
                  variable count : integer;
              begin

     To assign a value to a variable we use := like:
           
              count := 0;


    

    Basic data types
    In order to use objects in a program they need to have a data type.


    The most used data types in a VHDL program are std_logic and std_logic_vector. They are the most used because the values held by them are 0 or 1 witch represent binary numbers (the basis of digital electronics). Another value they can be given is Z witch stands for high impedance (it is used when designing circuits that communicate to a common bus).
    The difference between the 2 types is that std_logic holds a single value of 0 or 1 or Z and std_logic_vector is a unidimensional array of std_logics so it holds as many values as we want.
    Example declarations:
           
              signal enable : std_logic;
              signal num1 : std_logic_vector(7 downto 0);


    From the above we can determine that num1 is an array of 8 std_logic values (7 downto 0 means that it has 8 elements numbered from 7 to 0). Vectors can be used entirely by referencing the name (ex: num1), bit by bit using an index (ex num1(0) means the left most bit or least significant bit) or by chunks of several bits (ex: num1(2 downto 0) references the left most 3 bits)

    Examples of assigning values:
 
              enable <= '0';
              enable <= '1';
              num1 <= "10110001";
              num1 <= "00000000";
              num1 <= (others => '0');
              

    The examples are straight forward and ,I hope, don't need explaining.

    Basic operators used on these types are: not, and, nand, or, nor, xor, xnor.
    Also useful is the concatenation operator: &. Example of using &:

              --the result of the following expression is "11110000"
              num1 <= "1111"&"0000";


    If we want to perform more complex math functions on numbers three data types can be used: integer, signde and unsigned. The last 2 can be used only after including the numeric.std package.
    Integers are 32 bit numbers and basically any math operator can be performed on them (+,- ,*, / ,mod,rem) but it's hard to translate an integer into digital circuits. That's where the unsigned and signed types step in. They are formed with std_logic elements and can be used with all the operators that integers can be used with.
    For the most designs you will be using mostly unsigned because signed elements have the most significant bit dedicated to the sign of the number (+/-)

    All of these concepts will be clear when we will do more example designs. For now it's best to try to understand most of them.
    More advanced operators and data types will be presented in later tutorials.