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;
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;