Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Computacionales para
Aplicación Específica
Sesión 10
Ciclo: Agosto 2021
Logro de la sesión
¿Qué es modularidad?
Por ejemplo, un programa escrito en lenguaje C es modular cuando se delega las tareas a
diferentes funciones, en lugar de llevar a cabo todo el procesamiento en la función principal. Es
posible mejorar la modularidad del programa, separando las tareas no solamente en
funciones, sino también en archivos, de manera conveniente (cabeceras, librerías, drivers, etc.).
En los lenguajes de programación orientada a objetos, tales como Java o C++, es posible crear elementos
con características específicas. A este conjunto de características particulares se le conoce como CLASE, y
a cada uno de los elementos creados con este grupo de características se le conoce como OBJETO.
Cuando se crea un OBJETO que pertenece a una CLASE específica (es decir, con un conjunto de
características determinadas), se dice que estamos instanciando dicho objeto.
Por un lado, el conjunto de características de un objeto (la CLASE) se construye una sola vez. Por otro lado,
es posible crear o instanciar una infinita cantidad de objetos de una misma clase.
El lenguaje VHDL permite instanciar elementos conocidos como componentes. En este caso particular, en
lugar de “crear una clase”, lo que se hace es construir la implementación del componente una sola vez. Al
igual que con los objetos, los componentes pueden ser instanciados una infinidad de veces.
El lenguaje VHDL nos brinda recursos para llevar a cabo diseños complejos mediante el uso
de código organizado en “módulos”.
El objetivo principal del diseño modular es permitir que “bloques” comunes de código puedan ser
re-usados y compartidos.
Teniendo en cuenta los objetivos de nuestro curso, para el estudio del diseño modular,
solamente abordaremos el uso de los bloques conocidos como componentes
(COMPONENTS).
El estilo de código que separa un diseño VHDL en componentes, instancia “copias” de dichos
componentes y los interconecta en un archivo aparte, se conoce como código estructural.
Para ilustrar cada uno de los pasos mencionados anteriormente se empleará un ejemplo
aplicativo simple.
Imagine que se desea diseñar el circuito combinacional que se muestra en la imagen inferior.
En lugar de escribir todo el código VHDL en un solo archivo, organizaremos el circuito en 03
componentes: compuerta NOT, compuerta NAND de 02 entradas y compuerta NAND de 03
entradas.
• El primer paso es escribir el código VHDL de cada uno de los componentes de manera
convencional.
• Para esto se recomienda, primero, escribir el código del componente en un archivo VHD con
el nombre del componente, y asignar un nombre específico a la ENTIDAD y a la
ARQUITECTURA, de modo que no cause conflicto con los archivos VHD que se crearán más
adelante.
• Luego, se debe establecer el archivo creado como entidad TOP, compilar el código y
verificar que el diseño es correcto.
• Se debe repetir el procedimiento con cada uno de los componentes que se va a utilizar.
Una vez que los componentes han sido implementados, debemos declararlos e instanciarlos
en el archivo VHD donde se desea utilizarlos. Este archivo VHD, considerado el principal, es el
que contiene a la entidad TOP, la cual se llama así por estar en el nivel más alto de la jerarquía.
Imaginemos que, en nuestro ejemplo, el archivo VHD que contiene la entidad TOP se llama
“project.vhd” y es ahí donde deseamos utilizar los componentes. En ese caso, debemos
realizar la declaración y la instancia de los componentes en el archivo “project.vhd”.
COMPONENT nombre_componente IS
PORT(
nombre_puerto : modo_señal tipo_señal;
nombre_puerto : modo_señal tipo_señal;
…);
END COMPONENT;
Instanciar un componente puede definirse como crear un objeto que tiene todas las
características del componente. De hecho, la instancia de componentes tiene una sintaxis
que nos hace recordar a los lenguajes de programación orientados a objetos (Java, C++).
La lista de puertos (lista_puertos) indica la conexión entre los pines del componente y las
entradas, salidas o señales que se encuentran a nivel local (en el archivo VHD donde se
instancia los componentes).
Imaginemos que tenemos el inversor de nuestro ejemplo declarado en la entidad TOP, tal
como se muestra:
COMPONENT inverter IS
PORT (a: IN STD_LOGIC; b: OUT STD_LOGIC);
END COMPONENT;
Si deseamos instanciar el inversor, conectar su entrada “a” a una señal “x”, y conectar su
salida “b” a una señal “y”, podemos hacerlo mediante mapeo posicional, empleando la
siguiente sintaxis:
En el caso del mapeo posicional, los puertos “x” e “y” corresponden a “a” y “b”,
respectivamente. Para utilizar esta forma, debemos respetar el orden de las entradas/salidas
del componente.
Una manera alternativa de describir el mapeo de los puertos es mediante mapeo nominal.
En este caso es necesario utilizar, de manera explícita, los nombres de las entradas y salidas
del componente.
El mapeo posicional es más fácil de escribir, pero el mapeo nominal es menos proclive a
error. Además, los puertos de salida pueden dejarse “al aire”, usando la palabra clave OPEN.
U2: mi_circuito PORT MAP (a => x, b => y, w => OPEN, d => z);
--Declaracion de la entidad
ENTITY frequency_divider IS
PORT(
clk_in: IN std_logic;
clk_div: OUT std_logic
);
END frequency_divider;
--Definicion de arquitectura
ARCHITECTURE frequency_divider OF frequency_divider IS
SIGNAL count: unsigned(19 downto 0);
BEGIN
--Divisor de frecuencia
PROCESS(clk_in)
BEGIN
if(rising_edge(clk_in)) then
if(count = x"7A11F") then
count <= x"00000";
clk_div <= '1’;
else
count <= count + x"00001";
clk_div <= '0’;
end if;
end if;
END PROCESS;
END frequency_divider;
--Declaracion de la entidad
ENTITY debouncer IS
PORT(
clk, sw_in: IN std_logic;
sw_in_clean: OUT std_logic
);
END debouncer;
--Definicion de arquitectura
ARCHITECTURE debouncer OF debouncer IS
SIGNAL a, b, c, d: std_logic;
BEGIN
--Registros en serie
PROCESS(clk)
BEGIN
if(rising_edge(clk)) then
a <= sw_in;
b <= a;
c <= b;
d <= c;
end if;
END PROCESS;
--Compuerta AND
sw_in_clean <= b AND c AND d;
END debouncer;
--Declaracion de la entidad
ENTITY up_down_counter IS
PORT(
up_ndown: IN std_logic;
clk: IN std_logic;
q: OUT unsigned(3 downto 0)
);
END up_down_counter;
--Definicion de arquitectura
ARCHITECTURE up_down_counter OF up_down_counter IS
SIGNAL count: unsigned(3 downto 0);
BEGIN
--Contador ascendente/descendente
PROCESS(clk)
BEGIN
if(rising_edge(clk)) then
if(up_ndown = '1') then
count <= count + "0001";
else
count <= count - "0001";
end if;
end if;
END PROCESS;
END up_down_counter;
Bin2Gray: Bin_To_Gray GENERIC_MAP(n => 16) PORT MAP (b => entrada, g => salida);
Como ejemplo aplicativo, vamos a modificar el código del ejemplo anterior. En lugar de emplear
un contador ascendente/descendente de 04 bits, utilizaremos un contador genérico de ‘n’ bits, el
cual deseamos configurar para que tenga una salida de 08 bits.
El diagrama de bloques de este circuito se muestra en la imagen inferior. Para realizar los
cambios, solamente es necesario modificar los archivo VHD correspondientes al contador
ascendente/descendente y a la entidad TOP. Los archivos modificados se muestran en las
siguientes diapositivas.
--Declaracion de la entidad
ENTITY up_down_counter IS
GENERIC(n: integer:= 4);
PORT(
up_ndown: IN std_logic;
clk: IN std_logic;
q: OUT unsigned(n-1 downto 0)
);
END up_down_counter;
--Definicion de arquitectura
ARCHITECTURE up_down_counter OF up_down_counter IS
BEGIN
--Contador ascendente/descendente
PROCESS(clk)
BEGIN
if(rising_edge(clk)) then
if(up_ndown = '1') then
count <= count + UNO;
else
count <= count - UNO;
end if;
end if;
END PROCESS;
END up_down_counter;
Algunos circuitos, relativamente complejos, están conformados por una gran cantidad de
componentes de un mismo tipo, los cuales se encuentran interconectados entre sí. Esto es
particularmente cierto para los circuitos que se encargan de realizar operaciones matemáticas a
alta velocidad (e.g. sumadores y multiplicadores paralelos).
--Declaracion de librerias
LIBRARY ieee;
USE ieee.std_logic_1164.all;
--Declaracion de la entidad
ENTITY full_adder IS
PORT(
a, b, carry_in: IN std_logic;
sum, carry_out: OUT std_logic
);
END full_adder;
--Definicion de arquitectura
ARCHITECTURE full_adder OF full_adder IS
BEGIN
s1 <= a XOR b;
sum <= s1 XOR carry_in;
END full_adder;
Una vez que el componente full_adder ha sido implementado, el siguiente paso es instanciarlo
04 veces, e interconectar los componentes tal y como se muestra en la imagen inferior.
Debe notarse que, la salida carry_out de un sumador se conecta con la entrada carry_in del
sumador que se encuentra inmediatamente después. El circuito resultante presenta dos
entradas de 04 bits ‘X’ e ‘Y’, una entrada de un bit “C_IN”, una salida de 04 bits ‘S’ y una salida
de un bit “C_OUT”.
Por conveniencia, emplearemos una señal tipo vector para conectar los “carries” del circuito.
END full_adder_04_bits;
--definicion de arquitectura
ARCHITECTURE full_adder_04_bits OF full_adder_04_bits IS
--Declaración de componentes
COMPONENT full_adder IS
PORT(
a, b, carry_in: IN std_logic;
sum, carry_out: OUT std_logic
);
END COMPONENT;
--Declaración de señales
SIGNAL carries: std_logic_vector(2 downto 0);
Del código anterior, se puede notar que existe un patrón de repetición en la instanciación de los
componentes “fa1” y “fa2”. Sin embargo, este patrón no se presenta en la instanciación de “fa0”
y “fa3”.
Para aprovechar el uso de sentencias iterativas se debe tener un patrón de repetición que se
presente en una gran parte del circuito. Por esta razón, agregaremos dos elementos más al
vector “carries” y los conectaremos a los puntos correspondientes a “c_in” y “c_out”.
END full_adder_04_bits;
--Definicion de arquitectura
ARCHITECTURE full_adder_04_bits OF full_adder_04_bits IS
--Declaración de componentes
COMPONENT full_adder IS
PORT(
a, b, carry_in: IN std_logic;
sum, carry_out: OUT std_logic
);
END COMPONENT;
--Declaración de señales
SIGNAL carries: std_logic_vector(4 downto 0);
END full_adder_04_bits;
--Definicion de arquitectura
ARCHITECTURE full_adder_04_bits OF full_adder_04_bits IS
--Declaración de componentes
COMPONENT full_adder IS
PORT(
a, b, carry_in: IN std_logic;
sum, carry_out: OUT std_logic
);
END COMPONENT;
--Declaración de señales
SIGNAL carries: std_logic_vector(4 downto 0);
La nueva versión del código presenta la ventaja de ser fácilmente escalable. Es decir, si se
desea extender el diseño para construir un sumador de ‘n’ bits, esto se puede lograr fácilmente.
--Definicion de arquitectura
ARCHITECTURE full_adder_n_bits OF full_adder_n_bits IS
--Declaración de componentes
COMPONENT full_adder IS
PORT(
a, b, carry_in: IN std_logic;
sum, carry_out: OUT std_logic
);
END COMPONENT;
En las sesiones anteriores de nuestro curso hemos establecido que existen dos tipos de
sentencia: sentencia concurrente (WHEN simple, WHEN con selector, FOR GENERATE, entre
otros.) y sentencia secuencial (IF-ELSE, CASE, FOR LOOP, entre otros).
Por un lado, las sentencias secuenciales únicamente se pueden utilizar dentro de un PROCESS;
por otro lado, las sentencias concurrentes únicamente se pueden utilizar fuera de los PROCESS.
Esto nos hace plantearnos la pregunta ¿A qué tipo de sentencia pertenece la instanciación de un
componente? ¿Concurrente o secuencial?
Por esta razón utilizamos la sentencia FOR GENERATE para el diseño del sumador genérico, ya
que no es posible utilizar la sentencia FOR LOOP (una sentencia secuencial).
El diseño de sistemas digitales mediante el uso de código estructural también se conoce como
diseño jerárquico. Esto se debe a que es común organizar un sistema complejo en bloques, los
cuales se distribuyen en niveles o jerarquías.
Cuando se emplea esta distribución, un bloque que se encuentra en un nivel superior instancia a
uno o más bloques que se encuentra en un nivel inferior de la jerarquía. Para ilustrar mejor esto,
mostraremos la representación en niveles de un par de ejemplos vistos en clase.
En una representación jerárquica, siempre existirá un bloque que se encuentra en la parte más
alta de la jerarquía, el cual inicia la instanciación de los demás bloques. Debido a su ubicación
en la cima de la jerarquía, este bloque también recibe el nombre de bloque TOP o entidad TOP
(“top” en Inglés quiere decir cima o punto más alto).
Asimismo, los bloques que se encuentran en la parte más baja de la jerarquía son comúnmente
llamados bloques BOTTOM (“bottom” en inglés quiere decir fondo o punto más bajo).
Una vez que se conocen los conceptos de diseño jerárquico, bloque TOP y bloque BOTTOM, ya
se hablar de mtodologías de diseño.
En la metodología de diseño Top-Down, se define el bloque del nivel TOP y se identifican los
sub-bloques necesarios para para construir el bloque de nivel TOP. Luego, se subdividen los
sub-bloques, hasta llegar a los sub-bloques BOTTOM, los cuales no pueden ser divididos.
Esta metodología de diseño es la que utilizan por defecto quienes se inician en el diseño digital
con VHDL, ya que, inicialmente, no cuentan con un repertorio de componentes.
Esta metodología de diseño es particularmente útil cuando el diseñador cuenta con un repertorio
(o librería) de componentes, los cuales puede aprovechar.
• La modularidad permite organizar un diseño en bloques reutilizables, lo cual puede ahorrar tiempo
de desarrollo al diseñador, además de hacer el código más ordenado y legible.
• La instanciación de componentes puede emplearse en conjunto con los parámetros genéricos para
obtener diseños más versátiles.
[1] Stephen Brown & Zvonko Vranesic, Fundamentos de lógica digital con
diseño VHDL, Segunda Edición, McGraw Hill, 2006.
[2] Volnei A. Pedroni, Circuit Design with VHDL, Third Edition, The MIT
Press, 2020.
[3] Frank Vahid, Digital Design with RTL Design, VHDL and Verilog, John
Wiley & Sons, Second Edition, 2011.