miércoles, 23 de noviembre de 2016

DIVISION DE ENTEROS EN VHDL

La siguiente entrada es sobre el proyecto final del curso de digitales avanzadas, que su objetivo es diseñar e implementar un procesador a la medida que realice las operaciones de división y residuo entre dos números enteros, tomando como lenguaje de descripción  VHDL y como plataforma de implementación la tarjeta NEXYS 4.


Funcionalidad:
La entrada de dos datos se hará mediante un teclado matricial 4 x 4 (TM4) y la salida a través del siete segmentos (D7S). Al iniciar el sistema, mostrará en los D7S 1 y o  “00” y el resto de D7S apagados como indicativo de estar listo. La secuencia de operación es: ingresar el dividendo de dos dígitos (que mostrará en los D7S 5 permanecerá apagado). El sistema procederá a  realizar las operaciones y mostrara resultados: el cociente (que se mostrará en los D7S 3 y 2) y el residuo (que se mostrará en  D7S 0, el D7S 1 permanecerá apagado). Tener en cuenta que los teclados presentan el efecto de rebote que debe ser considerado para la correcta operación y que los D7S comparten un canal de datos común, así que deben operarse multiplexados.
A continuación iremos explicando cada contenido del problema que se nos ha puesto, para entender cómo se maneja el teclado matricial y el 7 segmentos  se debe indagar en la entrada anterior al blog correspondiente. Además hablaremos de cómo se  hace la división de enteros en VHDL.


DIVISIÓN:
Existen muchos tipos de algoritmos de división, a continuación uno que es el que resulta más preciso a nuestra forma de dividir a mano y es por tanto más sencillo de entender.

En la imagen  se va a mostrar la división entera realizada con números enteros decimales y números enteros binarios. 



Veamos paso a paso el algoritmo para hacer la división:
Primero desplazamos a la izquierda el divisor lo más que podamos siempre que “quepa” en el dividendo (D˃ d). La cantidad de veces que se ha desplazado la memorizamos. En electrónica digital, diríamos que los guardamos en un registro que llamaremos despl z. en el ejemplo de la imagen anterior el desplazamiento es 3.


Una vez que tengamos el divisor desplazado lo más posible a la izquierda desplz = 3, restamos al dividendo el divisor y ponemos un 1 en el cociente que se va a mostrar en la siguiente imagen. En la imagen que se va a mostrar en seguida se han rellenado los ceros que faltan a la derecha provocados por el desplazamiento. En el cálculo manual se hace implícitamente aunque no se ponen para ahorrar la escritura de la resta completa. 


Ahora el divisor, que hemos desplazado tres veces a la izquierda, lo desplazamos una vez a la derecha, es decir, como si del original lo desplazamos dos veces a la izquierda por tanto desplz = 2. Probamos si cabe con el resultado de la resta anterior, si cabe D ≥ d ponemos un 1 en el cociente. 

Volvemos a desplazar el divisor a la derecha ahora desplz = 1, y volvemos a ver si cabe en la nueva resta que hemos hecho.


Vemos que esta vez no cabe, por lo tanto pondremos un 0 en el cociente, no hacemos la resta, y probamos desplazando el divisor a la derecha  ahora desplz = 0  volvemos a como estaba en principio.


Tampoco cabe, y como el cociente ha vuelto a su posición original desplz = 0 la división se termina (pues no sacamos decimales). 


Ahora se desplaza los resto a la izquierda podríamos sacar decimales. En este caso podríamos sacar el primer decimal para calcular el redondeado del cociente.  Así que la implementación en hardware de este algoritmo se podrá hacer de manera secuencial, pero también se podría desarrollar y realizarla de manera combinacional  o segmentada.
Si se realiza de manera secuencial, la división se haría en varios ciclos de reloj, que dependerá del número de bits de los operadores. Por tanto, el divisor necesitara de una parte de control que ordene la ejecución de las operaciones que haya que realizar en cada momento. Una vez terminadas las operaciones deberá proporcionar una señal de aviso que indique que el resultado está disponible. Además el módulo, tendrá un señal de entrada que ordene la ejecución de la división.

MULTIPLEXACIÓN DEL 7 SEGMENTOS EN UNA FPGA
Un multiplexor o selector de datos es un circuito lógico que acepta varias entradas y solamente permite a una de ellas alcanzar la salida. La siguiente imagen muestra el diagrama de un multiplexor, donde se observa que la salida Z puede tomar el valor de A o B, pero no de ambos a la vez, en base al valor del parámetro de selección So. Un claro ejemplo de un multiplexor se encuentra en la televisión, donde solamente se muestra en pantalla el canal de deportes o el canal de música, pero no ambos a la vez (al menos hasta hace tres años claro está). 


Desde el punto de vista de programación, equivale a una simple estructura if - else, donde la variable puede tomar o uno u otro valor. Habiendo dicho esto, es claro que el multiplexor verse como una instrucción switch al incrementar la cantidad de los valores.
A continuación seguiremos con el diagrama de estados para implementar el código en VHDL  del problema propuesto.

DIAGRAMA DE ESTADOS 




Código de proceso de división

 Donde se ejecuta el proceso de tomar el dividendo que se compone de dos dígitos y empieza a restarse con el divisor tantas veces sea necesario hasta que ya el valor de dividendo sea menor que el divisor.

En este bloque es donde definimos la variables que son entradas y salidas.

  library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity divisor is
    port (
        clk     : in std_logic;
        reset   : in std_logic;
        NUM1  : in unsigned(7 downto 0);
        NUM2  : in unsigned(3 downto 0);
        DATO  : out unsigned(7 downto 0)
    );
end divisor;

En este bloque encontramos la arquitectura donde es define todas la funciones necesarias para que se realice y funciones el programa.

architecture divisor of divisor is
    type estado is (reposo, numero1, numero2, resta, cuenta, comparar, resultado);
    signal estadoActual, estadoSiguiente : estado;
    signal SNUM1 : unsigned(7 downto 0);
    signal SNUM2 : unsigned(7 downto 0);
    signal R : unsigned(7 downto 0);
    signal C : unsigned(7 downto 0);
    signal COS : unsigned(7 downto 0);

begin

En este siguiente bloque encontramos el camino de datos donde se definirán todas las transiciones de los estados que se realizaran en el programa. 
    -------------------------------   
    -- camino de datos (datapath)--
    -------------------------------

    process(clk, estadoActual, NUM1, NUM2)
      begin 
       if (clk'event and clk = '1') then
     
         case estadoActual is
         
           when reposo =>
             R <= "00000000";
             C <= "00000000";

           when numero1 =>
             SNUM1 <= NUM1;
             
           when numero2 =>
             SNUM2 <="0000" & NUM2;
             
           when resta =>
             R <= SNUM1-SNUM2;

           when cuenta =>
             C <= C+1;
             SNUM1 <= R;

           when comparar =>

           when resultado =>
             DATO <= C;
             COS <= R;

          end case;
        end if;
    end process;

En el siguiente bloque encontramos el registro de estados donde por cada flanco de subida del reloj se realizara un cambio de estado y si se oprime la señal de reset el programa se ira directamente a estado de reposo.
    -----------------------
    -- registro de estado--
    -----------------------

   process(clk, reset)
    begin
        if (reset = '1') then
            estadoActual <= reposo;
        elsif (clk'event and clk = '1') then
            estadoActual <= estadoSiguiente;
        end if;
    end process;

En el siguiente bloque encontramos los estados  donde se definen los estados lo que cada estado va a realizar en el programa.
    -------------
    -- Estados --
    -------------

    process (estadoActual, SNUM1, SNUM2) is
    begin
      case estadoActual is       
        when reposo => estadoSiguiente <= numero1;
        when numero1 => estadoSiguiente <= numero2;
        when numero2 => estadoSiguiente <= comparar;
        when comparar => 
            if (SNUM1 >= SNUM2) then
                estadoSiguiente <= resta;
            else
                estadoSiguiente <= resultado;
            end if;
        when resta => estadoSiguiente <= cuenta;
        when cuenta => estadoSiguiente <= comparar;
        when resultado => estadoSiguiente <= reposo;
     end case;
   end process;
end divisor;
        if (reset = '1') then
            estadoActual <= reposo;
        elsif (clk'event and clk = '1') then
            estadoActual <= estadoSiguiente;
        end if;
    end process;

    -------------
    -- Estados --
    -------------

    process (estadoActual, SNUM1, SNUM2) is
    begin
      case estadoActual is       
        when reposo => estadoSiguiente <= numero1;
        when numero1 => estadoSiguiente <= numero2;
        when numero2 => estadoSiguiente <= comparar;
        when comparar => 
            if (SNUM1 >= SNUM2) then
                estadoSiguiente <= resta;
            else
                estadoSiguiente <= resultado;
            end if;
        when resta => estadoSiguiente <= cuenta;
        when cuenta => estadoSiguiente <= comparar;
        when resultado => estadoSiguiente <= reposo;
     end case;
   end process;
end divisor;

martes, 22 de noviembre de 2016

MANEJO DE 7 SEGMENTO CON TECLADO MATRICIAL


La siguiente entrada es sobre la constitución de unas de las notas esenciales del curos que estamos en desarrollo para generar insumos que son útiles para el proyecto final que hablaremos en otra entrada en el blog. En este taller el objetivo es familiarizarse con el desarrollo de interfaces de entrada y salida en VHDL para la tarjeta Nexys 4.


FUNCIONALIDAD:
El propósito es conectar un teclado matricial 4 X 4 (TM4)  al puerto PMOD JA de la tarjeta Nexys 4. Cada vez que se presione una tecla, su valor se mostrara en el display de siete segmentos (D7S) numero  0, el ultimo visto desde la derecha del conjunto de display.
A continuación le mostraremos el funcionamiento del teclado matricial 4 X 4 que está constituido por una matriz de pulsadores dispuestos en filas (A, B, C, D) y columnas (1, 2, 3, 4), con la intensión de reducir el número de pines necesarios para su conexión.


TECLADO MATRICIAL
Su funcionamiento es muy sencillo, cuando se presione una tecla, se conectan internamente la fila y columna correspondiente; por ejemplo, al presionar la tecla “7” se conectan la fila c y la columna 1, si no hay ninguna tecla presionada, las filas estarán desconectadas de las columnas. 
 

Cuando se presiona una tecla de corriente puede fluir a través del switch y el voltaje de los pines conectados a las filas del teclado (5 V o nivel alto) aparece también ahora uno de los pines RB0 a RB3 según la columna en la que se encuentre la tecla oprimida. La siguiente imagen ilustra lo que sucede al oprimir una tecla: al oprimir el botón 6 provocamos un cambio en el pin RB2 que ahora recibe un 1 lógico o estado alto. Sabes entonces que se ha pulsado un botón en la segunda columna y se muestra como un nivel lógico alto aparece en los bits menos significativos del puerto B, específicamente en RB2. 


Diagrama de flujos del teclado matricial 

 

Display 7 segmentos
Es un componente que se utiliza para la representación de números o letras en muchos dispositivos electrónicos. La decodificación que se utiliza para pasar a un número de BCD a 7 segmentos tiene la siguiente combinación que va hacer ilustrada a continuación:

 

A continuación les mostramos el diagrama de bloques que representa la conexión del teclado matricial al display de 7 segmentos.

 

Le vamos a dejar la página donde pueden entrar y leer todo sobre la FPGA Nexys  4 Artix  https://sites.google.com/site/logicaprogramable/calculadoras/fpga/nexys-4-artix-7-fpga-board y así familiarizarse más a fondo sobre este dispositivo.
CODIGO EN VHDL  PARA IMPLEMENTAR EL PROBLEMA PLANTEADO:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL
entity teclado is
    port (
    ledfila, ledcol : out unsigned(3 downto 0);
        clk     : in std_logic;
        reset   : in std_logic;
            fila    : in unsigned(3 downto 0);
            col     : out unsigned(2 downto 0);
            display : out std_logic_vector(7 downto 0);
            seg7    : out std_logic_vector(6 downto 0)
    );
end teclado;

En esta primera parte del código se colocan las variables que son de salida o que son de entrada que son el reloj (CLK), reset, fila, columna (col), display es el que me define que display vamos a usar y  7 segmentos (seg7) es el que dice el número a mostrar.
architecture teclado of teclado is
    type estado is (reposo, inicio, cuenta, rotacion, deteccionFilaCol, deteccionTecla, mostrar);
    signal estadoActual, estadoSiguiente : estado;
    signal scol            : unsigned(2 downto 0);
    signal scol4 : unsigned(3 downto 0);
    signal nfila : unsigned(3 downto 0);
    signal tecla : unsigned(3 downto 0);
    signal multi : unsigned(7 downto 0);
    signal contador : unsigned(21 downto 0);
    constant U0L : unsigned(21 downto 0) := to_unsigned(0,22)
    constant U0 : unsigned(3 downto 0) := to_unsigned(0,4);
    constant U11: unsigned (3 downto 0) := to_unsigned (11,4);
    constant U1 : unsigned(3 downto 0) := to_unsigned(1,4);
    constant U2 : unsigned(3 downto 0) := to_unsigned(2,4);
    constant U3 : unsigned(3 downto 0) := to_unsigned(3,4);
    constant U4 : unsigned(3 downto 0) := to_unsigned(4,4);
    constant U5 : unsigned(3 downto 0) := to_unsigned(5,4);
    constant U6 : unsigned(3 downto 0) := to_unsigned(6,4);
    constant U7 : unsigned(3 downto 0) := to_unsigned(7,4);
    constant U8 : unsigned(3 downto 0) := to_unsigned(8,4);
    constant U9 : unsigned(3 downto 0) := to_unsigned(9,4);
    constant U12: unsigned(3 downto 0) := to_unsigned (12,4);
    constant U10: unsigned (3 downto 0) := to_unsigned (10,4);

En esta parte del programa ya viene la arquitectura, se habla sobre todos los estados que vienen siendo reposo, inicio, cuenta, rotación, decodificación fila-columna, detención tecla y mostrar. También se encuentran las señales como señal de estado actual y como señal de estado siguientes, donde se indican cada señal dependiendo de lo que se quiera realizar, tenga en cuenta que en VHDL siempre se tiene que manejar las variables del mismo tamaño.

begin
ledfila <= fila;
ledcol <= '1' & scol;
    display <= "11110110";
    col <= scol;
    multi <= U3*nfila;
    -- camino de datos (datapath)
    process(clk, estadoActual,tecla,scol,fila)
      begin
       if (clk'event and clk = '1') then
         case estadoActual is
             when reposo =>
                      scol <= "111"
  contador <= U0L;
             when inicio =>
                      scol <= "011";
             when cuenta =>
                  contador <= contador+1;
             when rotacion =>
                  scol <= scol(1 downto 0) & scol(2);
                  contador <= U0L;
                 when deteccionFilaCol =>
                  case fila is        
                      when "0111" => nfila <= U0;
                                      when "1011" => nfila <= U1;
                      when "1101" => nfila <= U2;
                      when "1110" => nfila <= U3;
                     when others => nfila <= U10;
                  end case;
                  case scol is        
                      when "011" => scol4 <= U0;
                              when "101" => scol4 <= U1;
                      when "110" => scol4 <= U2;
                      when others => scol4 <= U0;
                  end case;

                 when deteccionTecla =>
                  if fila(3)='0' then
                     tecla <= U0;
                  else
                     tecla <= scol4 + U1 + multi(3 downto 0);
                  end if;
             when mostrar =>
                  case tecla is        
                      when U11 => seg7 <= "1111001";  -- 1 working
                      when U8 => seg7 <= "0011001";  -- 4 working
                      when U5 => seg7 <= "0111000";  -- 7 working
                      when U12 => seg7 <= "0110000";  -- 3
                      when U7 => seg7 <= "0010010";  -- 5 working
                      when U4 => seg7 <= "0000000";  -- 8 working
                      When U10 => seg7 <= "0100100";  -- 2 working
                      --When U3 => seg7 <= "1110000"; -- no se
                      when U9 => seg7 <= "0000010";  -- 6 working
                      when U6 => seg7 <= "0011000";  -- 9 working
                      when others => seg7 <= "1000000";  -- *, #
                  end case;
           end case;
         end if;
    end process;

Seguimos con los caminos de datos que es lo que se va a realizar dentro de un estado, es decir que si en el estado de rotación se rota algún número eso es lo que se va a operar en el estado.
    -- registro de estado
    process(clk, reset)
    begin
        if (reset = '1') then
            estadoActual <= reposo;
        elsif (clk'event and clk = '1') then
            estadoActual <= estadoSiguiente;
        end if;
    end process;

    process (estadoActual,fila,contador) is
    begin

Continuamos con el registro de estado, en este estado lo que hace es guardar la variable y dándole funcionalidad al reloj, es decir que de un esta actual cambia al siguiente y que si se hace un reset vuelva a reposo
      case estadoActual is      
        when reposo => estadoSiguiente <= inicio;
        when inicio => estadoSiguiente <= cuenta;
        when cuenta =>
           if contador = "1111111111111111111111" then
               estadoSiguiente <= rotacion;
           else
           estadoSiguiente <= cuenta;
           end if;
        when rotacion =>
           if (fila = "1111") then
              estadoSiguiente <= cuenta;
           else
              estadoSiguiente <= deteccionFilaCol;
           end if;
        when deteccionFilaCol =>
              estadoSiguiente <= deteccionTecla;
        when deteccionTecla =>
              estadoSiguiente <= mostrar;
        when mostrar =>
              estadoSiguiente <= inicio;
     end case;
   end process;
end teclado;

Por último se encuentra el diagrama de estados donde se encuentra un process, donde son los procesos que están dentro de procesos que influirán sobre el mismo programa a realizar 


CONSTRAINT
set_property LOC E3 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports clk]
# Mapear RESET a BTND
set_property LOC V10 [get_ports reset]
set_property IOSTANDARD LVCMOS33 [get_ports reset]
set_property PACKAGE_PIN B13 [get_ports {col[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {col[0]}]
set_property PACKAGE_PIN F14 [get_ports {col[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {col[1]}]
set_property PACKAGE_PIN D17 [get_ports {col[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {col[2]}]
set_property PACKAGE_PIN G13 [get_ports {fila[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {fila[0]}]
set_property PACKAGE_PIN C17 [get_ports {fila[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {fila[1]}]
set_property PACKAGE_PIN D18 [get_ports {fila[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {fila[2]}]
set_property PACKAGE_PIN E18 [get_ports {fila[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {fila[3]}]
set_property PACKAGE_PIN L3 [get_ports {seg7[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg7[0]}]
set_property PACKAGE_PIN N1 [get_ports {seg7[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg7[1]}]
set_property PACKAGE_PIN L5 [get_ports {seg7[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg7[2]}]
#set_property SEVERITY {warning} [get_drc_checks NSTD-1]
#set_property SEVERITY {warning} [get_drc_checks UCIO-1]
set_property PACKAGE_PIN L4 [get_ports {seg7[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg7[3]}]
set_property PACKAGE_PIN K3 [get_ports {seg7[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg7[4]}]
set_property PACKAGE_PIN M2 [get_ports {seg7[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg7[5]}]
set_property PACKAGE_PIN L6 [get_ports {seg7[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg7[6]}]
set_property PACKAGE_PIN N6 [get_ports {display[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {display[0]}]
set_property PACKAGE_PIN M6 [get_ports {display[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {display[1]}]
set_property PACKAGE_PIN M3 [get_ports {display[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {display[2]}]
set_property PACKAGE_PIN N5 [get_ports {display[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {display[3]}]
set_property PACKAGE_PIN N2 [get_ports {display[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {display[4]}]
set_property PACKAGE_PIN N4 [get_ports {display[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {display[5]}]
set_property PACKAGE_PIN L1 [get_ports {display[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {display[6]}]
set_property PACKAGE_PIN M1 [get_ports {display[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {display[7]}]
set_property PACKAGE_PIN T8 [get_ports {ledcol[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ledcol[0]}]
set_property PACKAGE_PIN V9 [get_ports {ledcol[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ledcol[1]}]
set_property PACKAGE_PIN R8 [get_ports {ledcol[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ledcol[2]}]
set_property PACKAGE_PIN T6 [get_ports {ledcol[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ledcol[3]}]
set_property PACKAGE_PIN P2 [get_ports {ledfila[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ledfila[0]}]
set_property PACKAGE_PIN R2 [get_ports {ledfila[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ledfila[1]}]
set_property PACKAGE_PIN U1 [get_ports {ledfila[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ledfila[2]}]
set_property PACKAGE_PIN P5 [get_ports {ledfila[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ledfila[3]}]

En el constraint es la asignación de puerto de la FPGA, asignamos el puerto pmog que por donde nosotros conectamos el teclado matricial.
A continuación le ilustraremos la entidad del problema  y también el diagrama de estados: