Logo Studenta

Generacion de controladores difusos en FPGA basados en sintesis de alto nivel

¡Este material tiene más páginas!

Vista previa del material en texto

Generación de Controladores Difusos en
FPGA basados en Síntesis de Alto Nivel
Trabajo final de la carrera de ingeniería en sistemas de la Universidad del
Centro de la Provincia de Buenos Aires
por Luca Sarramone
Director: Dr. Martin Vazquez
Co-Director: Ing. Mg. Lucas Leiva
Tandil, Argentina, Febrero 2022
AGRADECIMIENTOS
A mi familia, por acompañarme en todo este proceso. A mis
amigos por aguantarme cuando peor estaba y alegrarse cuando
mejor me encontraba. A la facultad y maestros, por mostrarme
otros aspectos de la vida académica.
1
ÍNDICE
CAPÍTULO I: INTRODUCCIÓN
1.1 Contexto
1.2 Propuesta
1.3 Limitaciones y alcance
1.4 Organización
CAPÍTULO II: CONCEPTOS PRELIMINARES
2.1 Teoría de control y controladores clásicos
2.2 Controladores difusos
2.2.1 Logica difusa
2.2.2 Funciones de pertenencia
2.2.3 Fusificador
2.2.4 Base de reglas
2.2.5 Motor de inferencia difusa
2.2.6 Defusificador
2.3 Plataformas de implementación
2.4 FPGA
2.5 Flujo de diseño tradicional
2.6 Flujo de diseño moderno
2.7 Métricas y directivas
CAPÍTULO III: SÍNTESIS DE UN FLC
3.1 Fusificador
3.2 Motor de reglas
3.3 Defusificador
3.4 Ensamblaje general
3.5 Caso de uso: Péndulo invertido
CAPÍTULO IV: GENERADOR DE FLC
4.1 Abstracción de componentes y diseño general
4.2 Definición del lenguaje
4.3 Tabla de símbolos
4.4 Analizador Léxico
4.5 Analizador sintáctico
2
4.6 Generador de código
CAPÍTULO V: RESULTADOS
5.1 Péndulo invertido
5.2 Estacionamiento marcha atrás de un vehículo
5.3 Autoenfoque
5.4 Validación de resultados
CAPÍTULO VI: CONCLUSIÓN
6.1 Conclusión
6.2 Trabajos futuros
ANEXO A: Gramática del parser
ANEXO B: Código desarrollado para problema del péndulo invertido
ANEXO C: Código desarrollado para problema de estacionamiento.
ANEXO D: Código desarrollado para problema de autoenfoque.
Bibliografía
3
ÍNDICE DE FIGURAS
2-1. Controladores de lazo abierto y lazo cerrado
2-2. Componentes de un FLC y comunicación.
2-3 a. Arquitectura básica de una FPGA.
2-3 b. Conexiones programables de una FPGA.
2-4. Enfoque tradicional del flujo de diseño para FPGA.
2-5. Enfoque tradicional del flujo de diseño para FPGA.
3-1. Organización del resultado de un controlador con más de una variable de salida.
3-2. Esquema del problema de péndulo invertido
4-1. Arquitectura del generador de FLC.
4-2. Flujo de funcionamiento de la herramienta generadora.
4-3. Autómata finito para cadenas de caracteres.
4-4. Muestra de funcionamiento para Acción Semántica 3.
4-5. Autómata finito para cadenas de caracteres, con acciones semánticas.
4-6. Autómata finito para constantes enteras.
4-7. Autómata finito para constantes enteras, con acciones semánticas.
4-8. Autómata finito completo para constantes caracteres.
4-9. Autómata finito para comentarios de una línea, con acciones semánticas.
4-10. Autómata finito para comentarios multilínea, con acciones semánticas.
4-11. Manejo de saltos de línea, espacios y tabulaciones extra.
4-12. Autómata finito completo.
4-13. Diagrama de clases usado para las acciones semánticas.
4-14. Diagrama de clases estructura intermedia para sección Declare.
4-15. Diagrama de clases estructura intermedia para sección Fuzz.
4-16. Diagrama de clases estructura intermedia para sección Rules.
4-17. Diagrama de clases estructura intermedia para sección Defuzz.
5-1. Esquema del problema de estacionamiento de vehículo
5-2. Distintos tipos de estrategia de enfoque.
5-3. Proceso de validación de resultados.
4
ÍNDICE DE TABLAS
3-1. Análisis de estrategias para síntesis de un fusificador.
3-2. Resultados obtenidos para distintos algoritmos fusificadores.
3-3. Análisis de estrategias para síntesis de un motor de reglas
3.4. Resultados obtenidos para el defusificador Centroide.
3-5. Funciones difusas originales para problema de péndulo invertido.
3-6. Funciones difusas convertidas para problema de péndulo invertido
3-7. Matriz de reglas para problema de péndulo invertido.
3-8. Resultados obtenidos luego de síntesis del controlador para péndulo invertido.
4-1. Matriz de cambio de estados.
4-2. Matriz de acciones semánticas.
5-1. Tiempo de desarrollo para problema de péndulo invertido.
5-2. Funciones difusas para el problema de estacionamiento.
5-3. Matriz de reglas para el problema de estacionamiento.
5-4. Resultados síntesis de controladores para problema de estacionamiento.
5-5. Funciones difusas para el problema de autoenfoque.
5-6. Resultados síntesis de controladores para problema de autoenfoque.
5
CAPÍTULO I: INTRODUCCIÓN
1.1 Contexto
La lógica difusa (FL, Fuzzy Logic) es una lógica alternativa a la lógica clásica que pretende
introducir un grado de vaguedad a las funciones que evalúa. El objetivo es que el resultado tenga
que considerar el grado de incertidumbre que la mayoría de los fenómenos presentan en la
descripción de su naturaleza. Las funciones de pertenencia dentro de la teoría de conjuntos difusos
pueden adquirir valores en el rango 0 a 1, a diferencia de la teoría de conjuntos clásicos donde
únicamente se devuelven valores binarios.
Los sistemas que implementan este tipo de lógica son conocidos como controladores de lógica
difusa o FLC (por sus siglas en inglés Fuzzy Logic Controller). A diferencia de su contraparte
clásica, los controladores difusos no emplean modelos matemáticos complejos para su
funcionamiento. Esta característica los hace especialmente adecuados para procesos complejos y
mal definidos, en los cuáles utilizar un modelo analítico resulta difícil debido a la falta de
conocimiento en el dominio o porque las variables de entrada y salida no son medibles.
En los últimos años, para la implementación de controladores difusos tuvo gran aceptación el uso
de microcontroladores, DSPs (Digital Signal Processors) y FPGAs. En particular, estas últimas han
sido usadas de manera exitosa como soluciones de diseño de hardware en control industrial. El uso
de esta plataforma en el desarrollo de FLC’s presenta grandes ventajas, por su velocidad de
procesamiento, consumo de potencia, y especialmente por su flexibilidad de diseño y
reconfiguración. Tanto es así, que en este último tiempo la tendencia se dirige hacia el co-diseño de
plataformas Hardware/Software basado en descripciones y síntesis de alto nivel. Aún más, gracias a
las herramientas síntesis de alto nivel (HLS), hoy día es posible generar implementaciones RTL a
partir de lenguajes de alto nivel tipo C. La automatización de este proceso permite a los
desarrolladores concentrarse en el comportamiento del algoritmo, evitar errores y reducir el tiempo
de desarrollo.
1.2 Propuesta
Existen gran cantidad de herramientas para la síntesis en alto nivel. La mayoría de ellas utiliza C
o C++ como lenguaje para describir los diseños, lo que implica saber programar para utilizarlas. En
el contexto de controladores difusos, esto significa que además de los conocimientos propios en la
tecnología de FPGA y lógica difusa, es necesario conocer sobre programación en lenguajes de alto
nivel.
Por esta razón, en este trabajo se decidió el desarrollo de un generador automático de
controladores difusos en FPGA basados en síntesis de alto nivel. El objetivo es que esta herramienta
permita el diseño, implementación, testeo y calibración de FLC’s de manera ágil, en busca de
reducir los tiempos de desarrollo y los conocimientos de programación necesarios para usar las
plataformas de FPGA.
Al mismo tiempo, dado que las herramientas de síntesis en alto nivel permiten aplicar directivas
y diversas mejoras sobre los algoritmos, se plantea como objetivo secundario investigar las mejores
optimizaciones que se pueden aplicar sobre controladores difusos. Para ello se implementarán
6
distintas soluciones con diferentes combinaciones de directivas y se compararán los resultados
obtenidos durante la síntesis en base a su eficiencia y uso de recursos.
1.3 Limitaciones y alcance
El alcance de este trabajo está limitado al desarrollo de una primera versión de la herramienta
generadora,que cuenta con un número acotado de posibles algoritmos para aplicar en cada una de
las etapas del controlador. En específico, se estableció que el programa únicamente soporte
fusificadores de tipo triangular, trapezoidal, forma S, forma Z o singleton, defusificadores de tipo
Centroide y como método de evaluación a Mamdani.
Con respecto al objetivo secundario, las optimizaciones utilizadas se basan en las disponibles en
la herramienta de Xilinx Vivado HLS. Las pruebas fueron hechas únicamente sobre controladores
que utilizan los algoritmos mencionados anteriormente. Cabe la posibilidad de que los resultados
puedan extrapolarse a otros tipos de algoritmos, sin embargo dicho análisis no forma parte del
alcance del proyecto.
Queda también por fuera de los límites el testeo de los controladores en sistemas reales. El
análisis de resultados se hará en base a las métricas devueltas por la propia herramienta de Vivado
HLS, las cuales, cabe destacar, son bastantes cercanas a las obtenidas en la realidad.
1.4 Organización del informe
Además de la introducción, el informe puede separarse en cuatro grandes unidades:.
➔ La segunda unidad trata los conceptos preliminares sobre las tecnologías y algoritmos
usados a lo largo del proyecto.
➔ La tercera unidad se concentra en el objetivo secundario del trabajo: la realización de
pruebas en busca de las mejoras optimizaciones de controladores difusos en alto nivel.
➔ La cuarta unidad explica el proceso de desarrollo del generador de controladores difusos. Se
mencionan aquí distintos problemas que surgieron durante el desarrollo y las decisiones de
diseño que se tomaron para solucionarlos.
➔ En la quinta unidad se discuten los resultados obtenidos al usar la herramienta generadora en
distintos problemas de lógica difusa.
Finalmente, en la última unidad se realiza un resumen del proyecto y se proponen trabajos
futuros a modo de conclusión.
7
CAPÍTULO II: CONCEPTOS PRELIMINARES
En el siguiente capítulo se discuten las bases teóricas sobre las cuales
se realizó el proyecto. Inicialmente se introducen los conceptos más
básicos de la teoría de control y controladores clásicos, para luego
discutir las diferencias que presentan con respecto a los FLC. A
continuación se profundiza en la lógica difusa y los componentes de
un sistema difuso. Finalmente, se mencionan las plataformas
utilizadas en la implementación de controladores difusos, haciendo
especial énfasis en FPGA y su flujo de diseño. El objetivo de este
capítulo es proveer al lector un resumen de los conceptos que se irán
mencionando a lo largo del desarrollo.
2.1 Teoría de control y controladores clásicos
Al hablar de controladores [1], es necesario explicar previamente los conceptos básicos de la
teoría de control [2]. Esta área de estudio trata problemas de control en sistemas dinámicos, donde
hay involucradas máquinas y procesos complejos. El objetivo es usualmente desarrollar un modelo
o algoritmo capaz de controlar el sistema llevándolo a un estado objetivo de forma eficiente,
asegurando estabilidad y de la forma más óptima posible.
Bajo este paradigma es que surgen los controladores, sistemas programados para supervisar u
organizar un proceso específico. De manera resumida, su funcionamiento se basa en el uso de
sensores, que le permiten medir señales del ambiente con las cuales definir el estado actual del
proceso. Con esta información el controlador puede comparar el estado actual con el estado objetivo
al que quiere llegar y calcular los pasos necesarios para alcanzarlo. Para esta tarea hace uso de los
llamados actuadores, dispositivos tales como motores eléctricos, servomotores o motores
hidráulicos, que son capaces de modificar en alguna medida el proceso a controlar [1].
Según cómo interactúan todos estos componentes se pueden distinguir fundamentalmente dos
tipos de controlador: los de lazo abierto y los de lazo cerrado [3]. En el control de lazo abierto, la
salida del proceso no es tomada en cuenta por el controlador, es decir, no existe retroalimentación.
Un ejemplo de este tipo de lazo es el controlador de un lavaropa, el cual cambia entre etapas de
lavado en base a un timer y no según el estado de la ropa. Si bien este tipo de lazo es sencillo de
desarrollar, presenta varias limitaciones dadas las limitaciones que este tipo de sistema presenta, se
agrega un ciclo de retroalimentación convirtiéndolo en un control de lazo cerrado. Estos últimos
presentan varias ventajas, como mayor resistencia a perturbaciones o la capacidad de estabilizar
procesos inestables, pero también son más complejos y costosos de desarrollar. Dado que cada uno
de ellos posee sus propias ventajas y desventajas, por tanto utilizar uno u otro depende del tipo de
problema que se esté atacando. En particular, hoy día uno de los tipos de controlador más usado es
PID (Proportional-integral-derivative), ya que son considerados simples, confiables y fáciles de
entender [4].
8
Figura 2-1. Esquema general de funcionamiento para controladores de lazo abierto y lazo cerrado
Al resolver problemas de control clásico es posible definir un flujo de diseño general [5] que
resumidamente consta de cuatro etapas:
1. Se analiza el problema y se desarrolla un modelo matemático que lo describa de la mejor
manera posible. Dado que el mismo es una abstracción del proceso a controlar, nunca podrá
ser perfecto.
2. Usando el modelo desarrollado, o una versión simplificada del mismo, se diseña una
primera versión del controlador.
3. Se analiza el desempeño del controlador, usualmente vía simulación. Durante esta etapa es
posible que sea necesario rediseñar el controlador.
4. Se implementa el controlador en un dispositivo físico, y nuevamente se analiza su
desempeño. De vuelta, puede ser necesario rediseñar para solucionar los problemas que
surjan.
El proceso termina cuando el controlador implementado cumple con los estándares de
rendimiento y eficiencia establecidos.
2.2 Controladores difusos
Los controladores difusos [5][6] (FLC, Fuzzy Logic Controller) son sistemas que utilizan lógica
difusa para su funcionamiento. A diferencia de los sistemas de control clásicos, los FLC no
requieren de modelos matemáticos que describen la naturaleza del problema a resolver, sino que
funcionan mediante reglas sencillas que establecen una relación de causa-efecto. Esto los hace
sencillos de entender y permite el desarrollo de controladores confiables, sin la necesidad de un
estudio detallado del problema. Por esta misma razón es que las áreas donde FLC tiene gran
impacto son aquellas donde el dominio es desconocido, las variables involucradas tienen un gran
grado de incertidumbre o la información disponible no es suficiente como para desarrollar un
modelo matemático más exacto.
9
Un controlador difuso consta básicamente de cuatro componentes: fusificador, base de reglas
difusas, motor de inferencia difusa y defusificador. En la figura 2-2 puede observarse cómo cada
uno de estos módulos se comunican entre sí.
Figura 2-2. Componentes de un FLC y cómo se comunican.
Para cada una de estas etapas existen distintos tipos de algoritmos y, si bien hay ciertas opciones
que son objetivamente mejores que otras, no existe una configuración que funcione de forma
óptima en todos los ámbitos. Es por esto que la elección final dependerá del criterio del usuario y la
naturaleza del problema.
2.2.1 Lógica difusa
Antes de profundizar en los conceptos de controladores difusos, es necesario previamente
explicar qué es la lógica difusa [7]. La lógica difusa es una lógica multi-valuada que permite
reestructurar problemas de una manera más sencilla utilizando conjuntos de pertenencia en
lugar de la típica información binaria . La idea se basa en emular la habilidad de razonamiento
y poder obtener soluciones precisas a partir de datos aproximados.
La lógica difusa surge intuitivamente al observar el razonamiento humano. Muchos
métodos de análisis se basan en el uso de técnicas matemáticas, cuyas variables pueden
expresarse en valores numéricos. Sin embargo,el pensamiento humano no funciona de la
misma manera, sino que los valores que toman las “variables” involucradas son difusos. Por
ejemplo, el concepto de “calor” hace referencia a un conjunto de valores de temperatura, pero
dependiendo de la persona los límites pueden ser unos o otros, es decir, mientras para A el
calor implica una temperatura aproximada entre “25 °C y 30 °C”, para B el calor implica una
temperatura aproximada entre “28 °C y 35 °C”. Este concepto se conoce como “variable
lingüística” (ya que su valor no es un número sino una palabra) y juega un rol muy importante
en la teoría de conjuntos difusos y lógica difusa.
Ahora bien, si el concepto de calor es tan abstracto ¿Como podemos decir si una
temperatura es “caliente” o “fría”?. En la lógica booleana un valor pertenece o no a un
conjunto bien definido, no existe término medio. Siguiendo con el ejemplo se podría decir que
un día es caluroso si su temperatura es mayor a 25° grados. Expresado en forma matemática
esto sería:
𝐷𝑖𝑎𝐶𝑎𝑙𝑢𝑟𝑜𝑠𝑜 𝑡𝑒𝑚𝑝𝑒𝑟𝑎𝑡𝑢𝑟𝑎( ) = {𝑡𝑒𝑚𝑝𝑒𝑟𝑎𝑡𝑢𝑟𝑎 ≥ 25°}
Pero como ya se dijo anteriormente esto no sucede de la manera que las personas conciben
el mundo real. Si bien a B le resulta “caluroso” un día con 28° grados, no significa que para
10
un día con 27° piense lo contrario, ya que los conjuntos previamente definidos son
aproximados. Es necesario entonces establecer otra forma de evaluar los conjuntos difusos.
2.2.2 Funciones de pertenencia
Las funciones de pertenencia caracterizan a un conjunto y permiten evaluar si un elemento
X pertenece o no al mismo. Como ya se mencionó anteriormente, en la teoría clásica de
conjuntos dicha evaluación puede devolver únicamente dos valores: uno en caso de pertenecer
y cero si ocurre lo contrario. Sin embargo, en la lógica difusa las funciones de pertenencia
pueden tomar valores intermedios, es por esto que al graficarlas es posible distinguir tres
partes:
➔ Núcleo: Se denomina núcleo de un conjunto difuso A, al subconjunto de elementos
cuyo grado de pertenencia es completo.
➔ Soporte: Se denomina así al subconjunto de elementos que tenga cierto grado de
pertenencia distinto de cero al conjunto difuso A.
➔ Límite: Se denomina así al subconjunto de elementos con un grado de pertenencia al
conjunto difuso A no completo y distinto de cero.
Existen muchos tipos de funciones de pertenencia. Entre las más conocidas se encuentran
la función Triangular (definida en base a tres parámetros [a,b,c]), función Trapezoidal
(definida en base a los parámetros [a,b,c,d]), función Forma S (definida en base a los
parámetros [a,b]), función Forma Z (definida en base a los parámetros [c,d]) y Singleton
(definida en base a un único parámetro c) [6].
2.2.3 Fusificador
Las funciones de pertenencia mencionadas anteriormente son usadas por el fusificador del
controlador para “traducir” los valores numéricos a un valor de pertenencia. Este proceso
inicial es necesario ya que el motor de inferencia, la próxima etapa del control, trabaja bajo la
lógica difusa. Existen dos estrategias básicas para efectuar la evaluación de las funciones de
pertenencia:
➔ Orientada a memoria: Los grados de pertenencia se pre calculan y se almacenan en
memoria. Esto provoca que, a mayor detalle en los conjuntos discretos, se necesite
inevitablemente más espacio en memoria. Por otra parte, también permite que el tipo de
función no afecte la carga computacional.
➔ Orientado a cálculo: Los grados de pertenencia se evalúan en ejecución, en memoria
solo se almacenan los parámetros necesarios para el cálculo. Al contrario que el otro
método, si bien la carga de memoria no se ve afectada por la complejidad de la función,
si lo hace la carga computacional.
11
2.2.4 Base de reglas
La base de reglas permite definir el comportamiento del controlador. Una regla consiste en
una serie antecedentes que llevan a un consecuente, tal como si se tratara de una sentencia
IF-THEN. Las mismas pueden expresarse de la siguiente manera:
𝐼𝐹 𝑎𝑛𝑡𝑒𝑐𝑒𝑑𝑒𝑛𝑡𝑒
1
 𝐴𝑁𝐷 𝑎𝑛𝑡𝑒𝑐𝑒𝑑𝑒𝑛𝑡𝑒
2
 𝐴𝑁𝐷 ... 𝐴𝑁𝐷 𝑎𝑛𝑡𝑒𝑐𝑒𝑑𝑒𝑛𝑡𝑒
𝑝
 𝑇𝐻𝐸𝑁 𝑐𝑜𝑛𝑠𝑒𝑐𝑢𝑒𝑛𝑡𝑒
Por tanto, una base de reglas consiste en una serie de reglas del estilo:
𝑅1: 𝐼𝐹 𝑎𝑛𝑡𝑒𝑐𝑒𝑑𝑒𝑛𝑡𝑒
1
1 𝐴𝑁𝐷 𝑎𝑛𝑡𝑒𝑐𝑒𝑑𝑒𝑛𝑡𝑒
2
1 𝐴𝑁𝐷 ... 𝐴𝑁𝐷 𝑎𝑛𝑡𝑒𝑐𝑒𝑑𝑒𝑛𝑡𝑒
𝑝
1 𝑇𝐻𝐸𝑁 𝑐𝑜𝑛𝑠𝑒𝑐𝑢𝑒𝑛𝑡𝑒1
𝑅2: 𝐼𝐹 𝑎𝑛𝑡𝑒𝑐𝑒𝑑𝑒𝑛𝑡𝑒
1
2 𝐴𝑁𝐷 𝑎𝑛𝑡𝑒𝑐𝑒𝑑𝑒𝑛𝑡𝑒
2
2 𝐴𝑁𝐷 ... 𝐴𝑁𝐷 𝑎𝑛𝑡𝑒𝑐𝑒𝑑𝑒𝑛𝑡𝑒
𝑝
2 𝑇𝐻𝐸𝑁 𝑐𝑜𝑛𝑠𝑒𝑐𝑢𝑒𝑛𝑡𝑒2
...
𝑅𝑚: 𝐼𝐹 𝑎𝑛𝑡𝑒𝑐𝑒𝑑𝑒𝑛𝑡𝑒
1
𝑚 𝐴𝑁𝐷 𝑎𝑛𝑡𝑒𝑐𝑒𝑑𝑒𝑛𝑡𝑒
2
𝑚 𝐴𝑁𝐷 ... 𝐴𝑁𝐷 𝑎𝑛𝑡𝑒𝑐𝑒𝑑𝑒𝑛𝑡𝑒
𝑝
𝑚 𝑇𝐻𝐸𝑁 𝑐𝑜𝑛𝑠𝑒𝑐𝑢𝑒𝑛𝑡𝑒𝑚
O lo que es lo mismo pero de forma resumida:
𝑅𝐿: 𝐼𝐹 𝑎𝑛𝑡𝑒𝑐𝑒𝑑𝑒𝑛𝑡𝑒
1
𝐿 𝐴𝑁𝐷 𝑎𝑛𝑡𝑒𝑐𝑒𝑑𝑒𝑛𝑡𝑒
2
𝐿 𝐴𝑁𝐷 ... 𝐴𝑁𝐷 𝑎𝑛𝑡𝑒𝑐𝑒𝑑𝑒𝑛𝑡𝑒
𝑝
𝐿 𝑇𝐻𝐸𝑁 𝑐𝑜𝑛𝑠𝑒𝑐𝑢𝑒𝑛𝑡𝑒𝐿
𝐶𝑜𝑛 𝐿 = 1, 2, 3, …, 𝑚.
Asociado al antecedente y al consecuente es común encontrar un conjunto difuso, lo cual
se denota en forma genérica de la siguiente manera:
𝑣𝑎𝑟𝑖𝑎𝑏𝑙𝑒 𝐼𝑆 𝑐𝑜𝑛𝑗𝑢𝑛𝑡𝑜 − 𝑑𝑖𝑓𝑢𝑠𝑜
𝑖
Es importante mencionar que la comparación IS observada en las reglas no es del tipo
booleana. En la práctica, dicha expresión implica que la variable será evaluada en la función
de pertenencia del conjunto difuso i. Finalmente, reemplazando las variables de entrada como
ui, las de salida como v, los conjuntos difusos de entrada Fi y los de salida como G, se obtiene
la forma generalizada de las reglas que suele usarse en la literatura:
𝑅𝐿: 𝐼𝐹 𝑢
1
 𝑖𝑠 𝐹
1
𝐿 𝐴𝑁𝐷 𝑢
2
 𝑖𝑠 𝐹
2
𝐿 𝐴𝑁𝐷 ... 𝐴𝑁𝐷 𝑢
𝑃
 𝑖𝑠 𝐹
𝑃
𝐿 𝑇𝐻𝐸𝑁 𝑣𝐿 𝑖𝑠 𝐺𝐿
Cabe destacar que, si bien las variables con mayor cantidad de conjuntos difusos permiten
una mejor representación del dominio, también trae como consecuencia un aumento en la
cantidad de reglas y, por consiguiente un aumento en la complejidad de cálculo.
2.2.5 Motor de inferencias difusas
Es el encargado de, tal y como indica su nombre, realizar la inferencia difusa, proceso por
el cual se mapea una entrada difusa a una salida difusa con cierto grado de pertenencia. Para
ello evalúa en paralelo cada una de las reglas de la base descrita en la sección anterior.
Este proceso depende del tipo de algoritmo que se utilice. Uno de los más conocidos, es el
método Mamdani o Min-Max [8]. En el mismo, por cada regla definida se calcula el grado de
pertenencia de los valores de entrada a los conjuntos fuzzy antecedentes. De estos resultados
se obtiene el mínimo y luego, de dichos mínimos, se busca el máximo disponible para cada
12
posible conjunto difuso de salida. De esta manera se asocia un grado de pertenencia para cada
conjunto difuso de salida, los cuales deberán ser posteriormente defusificados.
2.2.6 Defusificador
El defusificador realiza el trabajo inverso al fusificador, es decir, “traduce” conjuntos
difusos a valores estándar. Esto es necesario ya que el motor de inferencia produce un
conjunto difuso de salida, y la solución del problema debe estar expresada en valores
numéricos. Dado que no existe una gran base científica para la defusificación, no es posible
afirmar que ciertos algoritmos destacan por sobre otros, y por tanto la elección de alguno de
ellos dependerá en gran medida del criterio del programador y del problema a atacar.
En particular, una de las técnicas más populares para defusificar lleva el nombre de
Centroide (Centroid) [9]. Es la versión simplificada de otro método, conocido también como
Centro de Gravedad (CoG, Center of Gravity) [10], donde se utilizan integrales para encontrar
el resultado. El método centroide plantea que la mayoría de las veces el soporte (S) de la
función de pertenencia μB(x) es discreto, y por tanto el resultado se puede aproximar mediante
sumatorias en lugar de integración. Dado que el cálculo ya no es tan preciso, en lugar de
calcularse el centro de gravedad se establece un punto aproximado denominado centroide Y
de B (siendo B el conjunto difusode la variable de salida) que luego se usa como solución del
sistema.
𝑖=1
𝑛
∑ 𝑦
𝑖
*µ
𝐵
(𝑦
𝑖
)
𝑖=1
𝑛
∑ µ
𝐵
(𝑦
𝑖
)
= 𝑌
2.3 Plataformas de implementación
Existen varias plataformas sobre las cuales se pueden implementar controladores difusos. Una de
ellas son los microcontroladores, dispositivos programables por software que ejecutan un programa
de forma concurrente. Están formados por un microprocesador, memorias y dispositivos de E/S
tales como temporizadores, conversores, etc. Si bien son reconfigurables, su capacidad de cómputo
es baja y por tanto su ámbito de aplicación se limita a problemas donde la potencia de cálculo no es
importante.
En el otro extremo se encuentran las ASIC [14] (Application-Specific Integrated Circuit),
circuitos integrados que se diseñan para una funcionalidad específica. El objetivo es poder plasmar
en hardware algún proceso específico para que se ejecute de la manera más rápida posible. Son
extremadamente eficientes, pero muy costosos y sin reconfiguración, por lo que se utilizan para
problemas muy específicos, donde la velocidad es clave.
Entre medio de estos dos se pueden ubicar los PLD [12] (Programmable Logic Device),
componentes electrónicos formados a partir de una serie de compuertas lógicas con conexiones
programables y usados para crear circuitos reconfigurables. Esto implica que mediante el uso de un
programa, se debe indicar al PLD como conectar sus componentes para recrear en hardware la
función que se necesita. Contrario a las computadoras, donde un procesador genérico ejecuta
13
instrucciones de un programa específico, los PLD están programados especialmente para ser usados
en una tarea, y si se desea utilizarlo en otro ámbito, no basta con escribir otro programa, sino que
también hay que reconfigurar sus conexiones
2.4 FPGA
Sin embargo, al hablar de controladores difusos no se puede dejar de mencionar a las FPGA [13]
(Field Programmable Gate Array), una plataforma de implementación que en los últimos años ha
ganado mucha aceptación, gracias a su capacidad de reconfiguración, en combinación con una gran
velocidad y un bajo consumo de potencia. Las FPGA son un esquema alternativo de PLD que tiene
mayor cantidad de componentes configurables, conexiones más flexibles y una memoria no volátil
que le permite almacenar su configuración. Su estructura básica consiste en una serie de bloques
lógicos programables (CLB), rodeados de lógica de interconexión programable. Cada CLB contiene
una serie de slices, que agrupan dos o más células lógicas, las cuales a su vez incluyen una LUT
(Look Up Table) para funciones simples, un registro y un multiplexor, entre otras cosas (figura
2-3a). Es evidente que estos elementos por sí solos no tienen un gran potencial, pero si se combinan
de la manera adecuada permiten generar funciones complejas. Para ello se utilizan las conexiones
programables que rodean a cada CLB (figura 2-3b). Las mismas están formadas por pistas metálicas
que recorren todo el dispositivo y se interconectan mediante transistores, que a su vez permiten
establecer uniones entre los segmentos usando únicamente un bit.
Figura 2-3 a. Izquierda, arquitectura básica de una FPGA, mostrando la división de CLB, lógica programable y
composición de los slices. b. Derecha, vista en profundidad de las conexiones programables de la FPGA.
Con el correr del tiempo las FPGA se fueron complejizando, agregando nuevos componentes
que dieran más flexibilidad a la programación, como bloques de memoria, módulos DSP y hasta
microprocesadores. Hoy día se pueden distinguir dos ramas de desarrollo: las FPGA clásicas y las
SoC FPGA. Las primeras respetan la idea original de diseño: dispositivos completamente formados
por lógica programable, mientras que las SoC FPGA integran un procesador genérico dentro del
chip. Este último enfoque presenta ciertas ventajas, como la adaptabilidad al cambio consecuencia
14
de la programabilidad de hardware y software, mejoras de performance y simplificación de
desarrollo gracias a la familiaridad con la programación en procesadores genéricos.
Aparte de su capacidad de reconfiguración, las FPGA presentan otras grandes ventajas. Una de
las más destacables es su bajo costo para el desarrollo en pocas unidades, que las hace
especialmente útil para prototipar y testear circuitos integrados. También gracias a esto, y a su
capacidad para implementar diseños complejos, son muy utilizadas para desarrollos donde son
necesarias piezas de hardware específico y el uso de circuitos integrados sería demasiado costoso.
Son de tamaño reducido, confiables y con gran tolerancia a errores, por lo que es común que sean
utilizadas en sistemas ciber-físicos o embebidos y en aceleración de procesos.
2.5 Flujo de diseño tradicional
A medida que las plataformas de FPGA se complejizaban, se hizo evidente la necesidad de
aumentar el nivel de abstracción para diseñar esquemas electrónicos de forma ágil, sin estar atados a
una arquitectura específica. Se pueden distinguir tres niveles de abstracción:
➔ Diseño a nivel de sistema: Se particiona el diseño en una parte de software y otra de
hardware, que se “co-diseñan” y “co-simulan” al mismo nivel.
➔ Diseño a nivel algorítmico: Las descripciones de algoritmos se generan usando lenguajes
de alto nivel y descripciones comportamentales usando lenguajes de descripción de
hardware. La simulación se hace en alto nivel.
➔ Diseño lógico: Tanto el diseño como la simulación se hacen a nivel compuertas lógicas. Se
utiliza análisis de tiempos y verificaciones formales.
Figura 2-4. Enfoque tradicional del flujo de diseño para FPGA.
En el enfoque tradicional de diseño (Figura 2-4), a partir de una especificación del problema se
realiza una partición de software / hardware manual. Esto separa el desarrollo en dos partes, para las
cuales existen herramientas específicas, usualmente ofrecidas por los propios fabricantes. Por
ejemplo, Xilinx ofrece las herramientas Vivado suite.
Dentro del flujo de diseño, la rama de software refiere a la programación del microprocesador
incluido dentro de la SoC FPGA [14]. Para ello se utilizan lenguajes en alto nivel orientados a
15
hardware, los cuales guardan una gran similitud con los lenguajes basados en C. Dentro de las
herramientas ofrecidas por Xilinx para esta tarea se encuentra Xilinx SDK (Software Development
Kit).
Por otro lado se encuentra la codificación en hardware, para la cual se utilizan lenguajes de
descripción de hardware [15] (HDL, Hardware Definition Language). Los lenguajes HDL son un
tipo especializado de lenguajes de programación usados para describir la estructura y
comportamiento de circuitos electrónicos. Si bien son similares estructuralmente a lenguajes más
abstractos como C, incorporan el concepto de tiempo, lo que permite a los programadores generar
diseños sincrónicos. Los estándares más utilizados en la actualidad son VHDL y Verilog. Vivado
cuenta con su propia herramienta para desarrollos en cualquiera de estos dos estándares, que
además incluye un catálogo de IP Cores [16]. Estos son fragmentos de código pregenerados,
similares a funciones, que permiten definir componentes del sistema de forma rápida. Gracias a esta
característica es posible diseñar un sistema usando módulos (o bloques IP) que se comunican
generando una cadena de procesamiento.
La conexión entre ambas partes del sistema se consigue mediante el uso de interfaces AXI [17]
(Advanced eXtensible Interface), una interfaz para comunicación paralela de alto rendimiento,
síncrona, de alta frecuencia, multi-maestra y multi-esclava. Fue diseñada con el objetivo de la
comunicación on-chip y cuenta con su propio protocolo estandarizado, que facilita la tarea de
conexión entre la parte lógica y la parte programable del dispositivo.
2.6 Flujo de diseño moderno
No obstante, hoy día el flujo de diseño en FPGA tiene una marcada tendencia hacia el diseño
completo en software de alto nivel. Un punto inicial para comenzar a hablar de este nuevo
paradigma se encuentra en las herramientasde síntesis de alto nivel (HLS, High Level Synthesis)
[18][19], que tienen por objetivo generar descripciones de hardware a partir de un código escrito en
C. Las necesidades de aumentar el nivel de abstracción para la síntesis de algoritmos son varias. En
primer lugar, la complejidad de los sistemas ha crecido exponencialmente, haciendo necesario
definir nuevas formas de diseño que estén desligadas de la arquitectura subyacente. Por otro lado,
diseñar de forma independiente a la arquitectura permite un aumento en la productividad, ya que
varias plataformas diferentes pueden programarse a partir de un mismo código. Finalmente, los
lenguajes de alto nivel son más comunes en la industria que los de descripción de hardware, por lo
que hay una mayor base de usuarios que podrá hacer uso de las tecnologías de FPGA con poca o
nula capacitación extra, sin contar que la aceleración de algoritmos por hardware puede hacerse en
forma directa al estar escritos en un lenguaje sintetizable.
Teniendo en cuenta que la lógica programable de un dispositivo puede ser configurada en alto
nivel usando este tipo de herramientas, se simplifica el manejo de particiones SW/HW al abstraer la
programación de ambas partes a un mismo nivel. Es por esto que surge un nuevo flujo de diseño
(figura 2-5), enfocado en la codificación simultánea de ambas particiones directamente desde alto
nivel. Este tipo de enfoque permite una partición SW/HW automática, lo que otorga más
transparencia para el programador y permite una mejor exploración del espacio de diseño. Entre las
herramientas disponibles, Xilinx ofrece SDSoC y SDAccel. Existen otras plataformas, como Xilinx
Pynq, que aumenta aún más el nivel de abstracción hasta el punto de permitir la programación
usando lenguajes como Python, o Vitis, orientado al uso de FPGA para inteligencia artificial.
16
Figura 2-5. Enfoque moderno del flujo de diseño para FPGA.
2.7 Parámetros y directivas
El diseño en alto nivel presenta ciertas desventajas con respecto a performance y otras
limitaciones con respecto a su capacidad de descripción. Para subsanar estas cuestiones existe lo
que se conoce como directivas [20], una serie de opciones que permiten aplicar optimizaciones
sobre el código de salida. Con respecto a la performance de los algoritmos se debe destacar que las
métricas [20] utilizadas son iguales a las usadas por herramientas de más bajo nivel que utilizan
lenguajes HDL para el diseño.
2.7.1 Métricas
➔ Latencia: La latencia se refiere al número de ciclos que se necesitan para generar el
resultado. Da cuenta de la velocidad del diseño (menos latencia, resultados más rápidos)
➔ Throughput: El throughput refiere número de ciclos entre nuevas entradas. Da cuenta
del grado de paralelismo/concurrencia en el diseño (menos throughput más instrucciones
procesandose concurrentemente)
➔ Recursos: Se distinguen cinco tipos de recursos en los reportes de Vivado HLS: BRAM,
DSP, FF, LUT y URAM. Cada placa tiene un número limitado de estos componentes, y a
su vez cada diseño ocupa un porcentaje de ellos.
2.7.2 Directivas:
➔ Pipeline: Reduce la latencia y el throughput al permitir la ejecución concurrente de las
operaciones dentro de una función o loop. Se considera un paralelismo de “grado fino”.
➔ Dataflow: Aumenta la concurrencia a nivel tarea, al permitir que distintas funciones o
llops se ejecuten de forma paralela. Reduce la latencia y el throughput del diseño.
17
➔ Function instantiate: Usada para crear implementaciones RTL únicas por cada instancia
de una función, permitiendo que cada una sea optimizada de forma local de acuerdo a su
invocación y manteniendo la jerarquía de funciones.
➔ Inline: Las funciones con inline se disuelven dentro del cuerpo de la función invocadora,
provocando que dejen de existir como un nivel separado en la jerarquía de funciones. En
otras palabras, se reemplaza los llamados por el cuerpo del método invocada.
➔ Unroll: La directiva de unroll transforma las estructuras iterativas al crear varias copias
del cuerpo dentro del mismo loop, permitiendo que instrucciones de iteraciones distintas
se ejecuten en paralelo.
➔ Loop flatten: Permite que loops anidados se fusionen en un único loop con mejor
latencia.
➔ Loop merge: Combina loops consecutivos en un único loop, reduciendo la latencia y
aumentando el paralelismo
➔ Loop tripcount: Usado sobre loops para limitar el número de iteraciones. Es usado
únicamente para análisis ya que no impacta en los resultados de la síntesis.
➔ Array map: Permite combinar varios arreglos pequeños en un único arreglo de mayor
tamaño para reducir el uso de recursos (BRAM).
➔ Array partition: Particiona un arreglo en otros de menor tamaño. Esto permite aumentar
la cantidad de puertos de entrada/salida en la memoria, y un potencial incremento del
paralelismo, a costa de un aumento en el uso de recursos.
➔ Array reshape: Combina los efectos de las dos directivas anteriores, particionando
arreglo en otros de tamaño más pequeños, y luego uniéndolos de manera tal que se
conserve el paralelismo y a su vez se reduzca el uso de recursos.
➔ Data pack: Usado para empaquetar los elementos de un struct dentro de un mismo
vector, reduciendo el uso de memoria requerido por variable y permitiendo que sus
elementos puedan leerse en paralelo.
18
CAPÍTULO III: SÍNTESIS DE FLC
En el siguiente capítulo se analizan las distintas optimizaciones que
existen para generar un controlador difuso en alto nivel. El objetivo es
encontrar las mejoras con mayor impacto, para luego replicarlas en
la herramienta generadora. Con este fin, se sintetizan los algoritmos
más usados en controladores difusos, aplicando sobre ellos diferentes
conjuntos de directivas. De esta manera, se obtiene una serie de
métricas (descritas en la sección 2.7) que permiten comparar las
distintas versiones generadas. Además, se toma como caso de uso el
péndulo invertido, para así verificar el impacto de estas mejoras sobre
un problema real.
3.1 Fusificador
La etapa de fusificación es la encargada de asignar un grado de pertenencia a las variables de
entrada, utilizando las funciones de pertenencia mencionadas en la sección 2.2.2. Dada la gran
variedad de algoritmos disponibles para esta tarea, el primer paso en la búsqueda de las mejores
optimizaciones es seleccionar un número reducido de ellos. Para este análisis se tomaron los tipos
de funciones difusas más comúnmente utilizados en los problemas de lógica difusa, los cuales son:
Triangular, Trapezoidal, Singleton, Forma S y forma Z.
Existen dos estrategias de implementación para los fusificadores, una basada en memoria y otra
basada en cálculo. Para analizar cuál es la más conveniente de utilizar, se calculó la complejidad
temporal y espacial de cada tipo de función en base a la cantidad de variables de entrada (M), la
cantidad de conjuntos difusos que las forman (N) y el dominio de dichos conjuntos difusos (D). Los
resultados pueden verse en la tabla 3-1.
Tabla 3-1
Complejidad temporal Complejidad espacial
O. a Memoria O. a Cálculo O. a Memoria O. a Cálculo
Desempeño O(1) O(M*N) O(M*N*D) O(1)
Tabla 3-1. Análisis de estrategias para síntesis de un fusificador.
Como era de esperarse, la complejidad temporal es mayor para el enfoque orientado a cálculo y
menor para el orientado a memoria, mientras que para la complejidad espacial ocurre lo contrario.
Teniendo en cuenta que los controladores difusos son usados en sistemas de tiempo real, la última
opción analizada pareciera ser la óptima. Sin embargo, es importante entender la implicancia que
tiene el dominio dentro de la complejidad espacial de la estrategia orientada a memoria.
Supongamos una función difusa f(x), de tipo Triangular con parámetros 10, 20 y 30. Esto significa
que en el rango [10;30] f(x) es distinta de cero, por tanto para cada valor que puede tomar x que se
encuentre dentro de dicho rango, se necesita calcular y almacenar el resultado de f(x). Asumiendo
que x únicamente pueden tomar valores enteros, habría que tener espacio suficiente para almacenar20 resultados fusificados. Ahora bien, un problema común de lógica difusa cuenta con al menos dos
variables, y cada una de ellas con tres o más conjuntos difusos asociados, sin contar que los
dominios de las funciones no suelen ser discretos. Mientras tanto, para el enfoque orientado a
cálculo, si bien la complejidad temporal también se verá afectada por estos factores, el uso de
19
directivas permite reducirlo drásticamente. Además, no se debe perder de vista que si se utiliza el
enfoque orientado a memoria, todos estos valores intermedios deben calcularse durante la
generación, lo cual enlentece el proceso calibración de controladores, que es en parte el objetivo
principal del proyecto. Como conclusión, si bien ambas opciones son más que viables, para esta
primera versión del trabajo lo óptimo es implementar un fusificador orientado a cálculo. No
obstante, no debe descartarse por completo la otra opción, ya que puede resultar útil en ciertos
contextos y sería positivo permitir al usuario elegir qué tipo de implementación desea.
Una vez establecidos estos puntos se comenzó a codificar los algoritmos en C++. Cada uno de
ellos se diseñó como una función individual, que por parámetro recibe el valor a fusificar y los
límites de la función. En este punto, es necesario tener en cuenta que al tratarse de sistemas
embebidos las variables de entrada son previamente convertidas a señales digitales por un conversor
A/D. Como consecuencia, el rango original de la variable se convirtió al rango [0; 2n-1], siendo n el
ancho en bits de los conversores utilizados, por lo que la precisión de los cálculos se verá
indudablemente afectada, especialmente si la variable de entrada puede tomar más de 2n valores. El
problema está en que esta cuestión no es atacable mediante software, ya que la conversión se hace
antes de que el controlador reciba las lecturas de los sensores; la única solución posible es aumentar
el tamaño de los conversores o limitar el rango de entrada. No obstante, teniendo en cuenta que la
lógica difusa trabaja con aproximaciones, utilizar valores poco precisos no debe considerarse una
problema crítico. Más aún, esta particularidad de los conversores puede ser explotada a favor, ya
que las operaciones entre números naturales requieren menos recursos, al no necesitar lógica extra
para valores negativos o con parte decimal. Basándose en este nuevo rango de valores, es posible
generar tipos con características específicas que se ajusten al problema de la manera más óptima.
Esto se consigue gracias a una serie de librerías especiales provistas por Vivado HLS. Las
propiedades que este nuevo tipo de dato, denominado fixed_int, son:
➔ Ancho fijo: Cantidad fija de bits, que coincide con el tamaño de los conversores.
➔ Sin signo: Dado que el rango es [0; 2n-1], los números negativos no se contemplan dentro
del dominio.
➔ Enteros: Sin bits reservados para la parte decimal.
Habiendo establecido el tipo que utilizaran las variables de cada método, se continuó con la
implementación comportamental de los mismos. Del subconjunto de tipos de funciones elegido al
inicio de la sección, se destacan dos características particulares. La primera de ellas es que todos las
funciones difusas son distintas de ceros en rangos limitados dentro del dominio total de la variable
de entrada. La segunda particularidad es que, dentro de ese rango, las funciones pueden dividirse en
distintas secciones que son fácilmente identificables usando sus parámetros y una serie de if-then.
En estas secciones, el valores de la función suele ser constante o toman la forma de una recta, cuya
fórmula es:
oµ
𝐿
(𝑥) = 𝑀 * ( 𝑥 −𝐿1 )𝐿2−𝐿1 µ𝐿(𝑥) =
𝑀 * ( 𝐿2 − 𝑥 )
𝐿1 − 𝐿2
Si se trata de una una recta creciente o decreciente respectivamente. Los límites L1 y L2 se
corresponden con los parámetros de la función, y M con el valor máximo alcanzable por los
conversores con ancho n (M = 2n-1). Teniendo en cuenta estas dos propiedades, fusificar un valor se
convierte en una tarea trivial, ya que los métodos únicamente deberán ubicar en qué segmento de la
20
función se ubica el valor de entrada (usando condiciones del tipo if-then), y luego utilizar la fórmula
correspondiente para calcular el valor de retorno.
Ahora bien, dado que las divisiones son muy costosas en términos computacionales, es posible
reescribir las ecuaciones con el objetivo de conseguir una mejor performance de los algoritmos.
Para ello se toman las partes constantes, se pre calculan durante la generación y se almacenan como
constantes en el código, tal como se ve a continuación:
µ
𝐿
(𝑥) = 𝑃
1
* (𝑥 − 𝐿
1
) 𝑐𝑜𝑛 𝑃
1
= 𝑀
(𝐿
2
−𝐿
1
)
µ
𝐿
(𝑥) = 𝑃
2
* (𝐿
2
− 𝑥) 𝑐𝑜𝑛 𝑃
2
= 𝑀
(𝐿
1
−𝐿
2
)
De esta manera, en tiempo de ejecución, en lugar de resolver dos restas y una división, el cálculo
se reduce a una resta y una multiplicación. Notar que el término constante que se despeja coincide
con la pendiente original de la recta, y que la segunda pendiente debería ser negativa, pero en su
lugar se invierten los operandos de la resta en μL(x), tal como si se hubiera extraído (-1) como factor
común.
En este punto ya no es posible continuar el desarrollo de las funciones, por lo que para obtener
mejores resultados es necesario utilizar directivas. La primera de ellas es la directiva Pipeline, que
permite la ejecución paralela de las distintas operaciones, siempre y cuando no haya elementos que
deban ejecutarse secuencialmente. Esto permite una gran mejora, sin embargo existe un problema
que es fácil de pasar por alto al analizar la performance individual de cada algoritmo, y es que en un
controlador varias funciones fusificadoras deben ejecutarse en paralelo. Por ejemplo, si se tiene una
variable Temperatura formada por tres conjuntos difusos (Caluroso, Templado y Fresco), el
fusificador debe calcular qué grado de pertenencia tiene el valor de entrada para cada uno de ellos.
Es decir, el código se vería similar a esto:
function fusificador ( var inVar ){
fuzzCaluroso = fuzz(inVar);
fuzzTemplado = fuzz(inVar);
fuzzFrio = fuzz(inVar);
}
El punto es que la directiva Pipeline permite paralelizar las instrucciones dentro de cada función
individual, pero por la forma en que cada una de ellas se invoca, la ejecución termina siendo
secuencial. En otras palabras, es necesario paralelismo a nivel instrucción y paralelismo a nivel
funciones. La primera ya fue conseguida; para la segunda existen dos estrategias:
1. Invocar a cada método con sus respectivos parámetros, tal como se ve en el ejemplo, pero
agregando a la función fusificador las directivas de Function Instantiate y Pipeline. De esta
manera cada llamado se considera un método independiente del resto, y gracias a la
directiva de pipeline se explota el paralelismo al máximo.
2. Cada una de las invocaciones en el ejemplo se reemplaza por el cuerpo del respectivo
método dentro de la función fusificador, a la que a su vez se le agrega la directiva Pipeline.
De esta manera el pipeline se aplica por sobre cada una de las operaciones y se consigue un
21
paralelismo más fino. Un efecto similar se puede conseguir manteniendo el ejemplo y
agregando sobre la función fusificador la directiva Inline.
Se optó por elegir la segunda opción ya que sus resultados son ligeramente mejores que los de la
primera opción. Luego de una serie de pruebas, se puede concluir que para la etapa de fusificación
los mejores resultados se obtienen con las siguiente configuración:
➔ Un método fusificador que contenga cada una de las funciones fusificadoras.
➔ Sobre dicho método debe aplicarse la directiva Pipeline
➔ Los cálculos de fusificación deben realizarse de forma directa, sin invocaciones, para que el
paralelismo sea más fino.
En la tabla 3-2 pueden verse los resultados obtenidos para cada tipo de método fusificador, tanto
para la versión sin mejoras (v1), como para la versión mejorada (v2). La síntesis se realizó
utilizando la placa xc7z020clg400-1, usando Vivado HLS versión 2020.1.
Versión Clk (ns)
Latencia(ciclos) Intervalo (ciclos)
BRAM DSP FF LUT
Min Max Min Max
Forma S v1 8.621 3 39 3 39 0 6 4528 6794
Forma S v2 7.333 1 1 1 1 0 0 0 96
Forma Z v1 8.621 3 39 3 39 0 6 4528 6794
Forma Z v2 7.333 1 1 1 1 0 0 0 96
Triangular v1 8.621 3 39 3 39 0 6 4629 7082
Triangular v2 7.333 1 1 1 1 0 0 0 165
Trapezoidal v1 8.621 3 40 3 40 0 6 4632 7136
Trapezoidal v2 7.333 1 1 1 1 0 0 0 190
Singleton v1 6.438 7 7 7 7 0 0 555 1250
Singleton v2 1.551 1 1 1 1 0 0 0 11
Tabla 3-2. Resultados obtenidos para distintos algoritmos fusificadores.
Como se puede observar, la latencia e intervalo de todas las funciones implementadas se reduce a
únicamente un ciclo en la versión optimizada. La información obtenida también demuestra que de
los recursos utilizados originalmente, luego de las mejoras se requieren únicamente LUT’s para la
misma función. Es interesante observar cómo estas mejoras afectan incluso al tipo Singleton,
teniendo en cuenta que la versión original podría considerarse óptima y que su función únicamente
consta de una comparación entre dos valores.
3.2 Motor de reglas
La próxima etapa del controlador es el motor de reglas difusas, que toma los valores de la etapa
anterior y, en base a una serie de reglas, calcula los grados de pertenencia de cada variable de salida.
Existen una gran cantidad de algoritmos de evaluación de reglas, pero el más destacable, y por tanto
22
el que se implementó en esta primera etapa del proyecto, es conocido como método Mamdani o
MinMax. Para su implementación se distinguen dos acercamientos. El primero es mediante
matrices, donde cada combinación posible entre los valores de entrada y su resultado se identifica
inequívocamente con las coordenadas de la matriz. La segunda estrategia, por otra parte, busca
identificar cada combinación mediante una serie de if-then. Por ejemplo, para un problema con dos
variables de entrada y una de salida, utilizando el primera enfoque las filas de la matriz se
corresponderían con los conjuntos de la primera variable de entrada, las columnas con los segunda
variable de entrada y las casillas contendrían los conjuntos difusos de salida correspondientes a esa
combinación de valores, mientras que, para la segunda estrategia, cada posible combinación de
entrada sería una condición de if, en cuyo cuerpo se establecería el resultado de salida. Para poder
comparar ambas opciones, se realizó un cálculo de complejidad temporal y espacial, basándose en
la cantidad de variables de entrada (M) y la cantidad de conjuntos difusos que las forman (N), que
arrojó los siguientes resultados:
Tabla 3-1
Complejidad temporal Complejidad espacial
Matrices If-Then Matrices If-Then
Desempeño O(NM) O(M*NM) O(NM) O(1)
Tabla 3-3. Análisis de estrategias para síntesis de un motor de reglas.
Como se puede observar, en el enfoque de matrices, la complejidad temporal producto de evaluar
todo el conjunto de reglas es igual a recorrer toda la estructura, mientras que para la estrategia de
if-then, en cada una de las NM reglas, se debe realizar un total de M comparaciones para identificar la
combinación de entrada, lo cual resulta bastante más ineficiente. Este último valor puede reducirse
utilizando condiciones anidadas, bajando la complejidad temporal de la estrategia if-then a O(NM),
sin embargo el algoritmo resultante no solo sería complejo de conseguir, sino también poco legible.
Por otro lado, la necesidad de almacenar en memoria las matrices hace que la primera estrategia
tenga una complejidad espacial de O(NM), mientras que la de su contraparte es constante. Si bien a
la hora de elegir una de las estrategias es posible argumentar que el rendimiento del algoritmo se
debe priorizar antes que el espacio que ocupa, es importante también tener en consideración las
capacidades representativas de cada una de ellas. El enfoque que utiliza if-then permite eliminar o
redefinir reglas, algo muy útil en ciertos problemas de lógica difusa, y algo que no se puede
conseguir utilizando matrices. Dado que el objetivo principal de este proyecto es la creación de un
generador de controladores, tanto la flexibilidad del lenguaje, como su capacidad para representar la
mayoría de problemas difusos es algo a lo que se le debe dar especial importancia, y por tanto la
opción uno queda descartada.
Habiendo definido el método y estrategia del motor de reglas, se comenzó su implementación y
la aplicación de directivas. Inicialmente se debe mencionar que los valores con los que operará el
motor son las variables que han sido fusificadas en la etapa anterior, y por tanto esta etapa del
controlador utilizará los mismos tipos predefinidos. En este punto surge un problema, y es que si
bien es posible definir un tipo de algoritmo evaluador, la base de reglas sobre las que funcionara
depende del problema elegido. Como consecuencia, definir las mejores optimizaciones depende
más del análisis teórico que de las pruebas que se puedan realizar. Aún así, como se verá más
adelante, distintas pruebas fueron aplicadas sobre un caso de uso y se demostró empíricamente que
las optimizaciones realizadas eran realmente las mejores.
23
En el método Mamdani, inicialmente se agrupan las reglas según su consecuente, luego se toma
el valor de pertenencia mínimo de los antecedentes de cada regla, y del subconjunto resultante de
esta operación se buscan los máximos para cada subgrupo consecuente. Es por esta secuencia de
minimo y maximo que también se conoce a este algoritmo como MinMax. Lo primero que se debe
mencionar aquí es que, por la estructura de C, encontrar el mínimo o el máximo implica realizar
comparaciones de a pares. Como consecuencia, las reglas que contengan el mismo consecuente
pueden ejecutarse de forma serial sin ningún problema, ya que para obtener el máximo es necesario
encontrar todos los valores anteriores. Bajo estas condiciones, el proceso de evaluación de reglas
consiste en una variable auxiliar, donde se almacena el mínimo de cada regla y luego se contrasta
con el mayor valor encontrado hasta ese punto. Por su parte, estos valores máximos se irán
almacenando en los arreglos usados para comunicar las etapas de evaluación de reglas y
defusificación. De esta manera se minimiza la cantidad de variables auxiliares necesarias, además
de asegurar que al evaluar la última regla, el arreglo de salida ya contenga los máximos encontrados
para cada conjunto difuso.
Con respecto a las optimizaciones, la primera directiva lógica para aplicar es Pipeline, ya que
permite paralelizar el cálculo de todas las operaciones. Nuevamente, como sucedió con los
fusificadores, para obtener el mayor potencial es necesario que las funciones encargadas de calcular
el mínimo y el máximo no se invoquen, sino que se calculen de forma directa en la función
principal. Otra cuestión que se debe tener en cuenta, es que para poder conseguir un mayor grado de
paralelismo, las estructuras intermedias que comunican la etapa de fusificación con la de evaluación
de reglas debe permitir un acceso simultáneo a los datos. Si bien, al analizar cada etapa de forma
individual se vuelve imposible controlar estas cuestiones, es primordial tenerlas en cuenta para más
adelante.
3.3 Defusificador
La etapa final, conocida como defusificación, toma los valores de salida del motor de reglas y los
convierte nuevamente a valores Crisp o clásicos. Al igual que con etapas anteriores, existen una
gran variedad de algoritmos disponibles para defusificar. Para este trabajo se decidió utilizar uno de
los más populares, el defusificador Centroide. Su funcionamiento consiste en una suma de
resultados parciales, donde en el numerador se incluyen los productos entre el grado de pertenencia
y el valor de salida correspondiente y en el denominador únicamente los grados de pertenencia. La
fórmula que describe a este algoritmo puede verse en la sección 2.2.6 de este trabajo.
En este caso, a diferencia de los anteriores, no existen distintas estrategias de implementación,
por lo cual el proceso de diseño comienza directamente con el análisis detipos de variables. En
primera instancia se debe tener en cuenta que los grados de pertenencia obtenidos a partir del motor
de reglas se encuentran agrupados en alguna estructura intermedia, cuyos elementos son del mismo
tipo usado hasta el momento por todas las etapas: fixed_int. Para poder almacenar la sumatoria del
numerador y denominador es necesario tener dos variables específicas, cuyo tamaño dependerá de
la cantidad de bits asignada para el tipo predefinido ya mencionado. Sabiendo que m es la cantidad
de conjunto difusos de salida y b la cantidad de bits de fixed_int, y teniendo en cuenta que el
resultado de una multiplicación binaria tiene un tamaño igual a la suma de los bits de sus operandos,
se puede calcular el tamaño de los productos parciales el numerador de la siguiente manera:
𝑇(𝑝𝑟𝑜𝑑𝑢𝑐𝑡𝑜 𝑝𝑎𝑟𝑐𝑖𝑎𝑙) = 𝑇(𝑔𝑟𝑎𝑑𝑜 𝑝𝑒𝑟𝑡𝑒𝑛𝑒𝑛𝑐𝑖𝑎) + 𝑇(𝑣𝑎𝑙𝑜𝑟 𝑑𝑒 𝑠𝑎𝑙𝑖𝑑𝑎) = 𝑛 + 𝑛 = 2 * 𝑛
Siendo T(x) el tamaño en bits de x
24
Ahora bien, estos resultados parciales deben sumarse, y se sabe que la suma de dos variables
binarias con el mismo tamaño devuelve un valor de tamaño igual a la cantidad de bits de sus
operandos más uno por el desborde, por tanto una suma de m operandos tendrá m bits de desborde.
Se concluye entonces que, el tamaño del numerador es igual a:
𝑇(𝑛𝑢𝑚𝑒𝑟𝑎𝑑𝑜𝑟) = 𝑇(𝑝𝑟𝑜𝑑𝑢𝑐𝑡𝑜 𝑝𝑎𝑟𝑐𝑖𝑎𝑙) + 𝑚 = 2 * 𝑛 + 𝑚
Por otra parte el denominador es igual a la suma de grados de pertenencia, por tanto su tamaño es
menor al del numerador:
𝑇(𝑑𝑒𝑛𝑜𝑚𝑖𝑛𝑎𝑑𝑜𝑟) = 𝑇(𝑔𝑟𝑎𝑑𝑜 𝑝𝑒𝑟𝑡𝑒𝑛𝑒𝑛𝑐𝑖𝑎) + 𝑚 = 𝑛 + 𝑚
Si bien es posible utilizar dos tipos distintos para el numerador y el denominador, la herramienta
de Vivado HLS responde de forma más eficiente cuando las operaciones se hacen entre dos
variables del mismo tipo. Como consecuencia se decidió la creación de un único nuevo tipo llamado
fixed_aux, cuyo tamaño será igual al tamaño del numerador. Cabe mencionar que la división
devuelve un resultado del mismo tipo, por lo que se deben extraer los n bits de menor peso para que
coincida en tamaño con fixed_int.
Con esto establecido, sólo resta verificar cuales directivas se ajustan mejor al problema. Es
importante analizar en este punto el hecho de que las sumas del numerador y denominador
dependen de los resultados parciales anteriores, por tanto las iteraciones se deben ejecutar en forma
serial y, como consecuencia, directivas tales como el Loop Unroll no surten ningún efecto. No
obstante, es posible conseguir cierto paralelismo a nivel instrucción, y para ello nuevamente se debe
aplicar Pipeline directamente sobre el método. Es posible también aplicar esta directiva sobre la
estructura iteradora (el for encargado de calcular cada suma parcial), pero las métricas obtenidas
durante la simulación indican que dicha configuración no consigue mejores resultados. Ahora bien,
al igual que en el motor de reglas, para explotar al máximo este paralelismo por instrucción, es
necesario que la comunicación entre las etapas permita el acceso simultáneo a los datos. Al mismo
tiempo, estos datos deben estar contenidos en una estructura sencilla de acceder para que las sumas
parciales pueden calcularse de forma general y no en base a variables específicas. Es por estas
razones que se decidió utilizar arreglos intermedios, que en conjunto con la directiva de Array
Partition, permiten recuperar cada variable de forma simultánea sin perder la agilidad de acceso que
provee la estructura del arreglo.
En la tabla 3-4 es posible ver el grado de mejora obtenido luego de las optimizaciones. La
síntesis se realizó utilizando la placa xc7z020clg400-1, usando Vivado HLS versión 2020.1. El
problema seleccionado cuenta con una variable de salida, formado por siete conjuntos difusos. Se
seleccionó esa cantidad porque es lo suficientemente alta como para obtener una buena descripción
de la variable de salida, y lo suficientemente baja para que el proceso sea eficiente.
Versión Clk (ns)
Latencia (ciclos) Intervalo (ciclos)
BRAM DSP FF LUT
Min Max Min Max
Sin mejoras 8.621 122 122 122 122 0 14 4471 5667
Con mejoras 9.400 22 22 1 1 0 4 910 807
Tabla 3-4. Resultados obtenidos para el defusificador Centroide.
Como se puede ver, según la latencia la nueva versión es más de cinco veces más rápida, y con
un intervalo de únicamente un ciclo. Además, al utilizar tipos predefinidos, el uso de recursos se
25
reduce drásticamente, especialmente en la cantidad de FF y LUT necesarias para implementar el
algoritmo. Es importante mencionar que el ejemplo simulado únicamente cuenta con una variable
de salida, pero puede ocurrir que un defusificador deba encargarse de traducir a valores Crisp más
de una variable. A su vez, cada una de ellas pueden utilizar algoritmos diferentes, algo similar a lo
que sucede en el fusificador. Para solucionar esto se decidió que cada variable de salida tuviera su
propio método separado, y que la salida final del controlador tuviera un tamaño específico de m*n
bits (siendo m la cantidad de variables de salida y n el tamaño del conversor). Los resultados de
cada defusificador ocuparan un espacio de bits determinado dentro de ese resultado y el usuario
puede luego tomar los bits correspondientes a cada variable, tal como se muestra en la figura 3-1.
Figura 3-1. Organización del resultado de un controlador con más de una variable de salida.
Esta forma de resolver la problemática de variables con distinto tipo de defusificación se eligió
no por ser la más eficiente, sino por ser la más flexible. Si bien el único algoritmo defusificador
propuesto es el Centroide, en un futuro esta situación puede cambiar y es necesario que el generador
esté preparado para ello. Cuando esto suceda, es posible investigar en base a las distintas opciones
defusificadoras, optimizaciones específicas para cada controlador. Por ejemplo, si un controlador
cuenta únicamente con algoritmos centroides es posible que todos ellos se calculen dentro de un
mismo método y que los loops de cada función se comprimen en una única estructura usando la
directiva Loop Merge. Dichas mejoras no fueron aplicadas ya que no forman parte del alcance de
este trabajo y quedan para un trabajo futuro.
3.4 Ensamblaje general
Finalmente resta ejecutar cada una de las etapas con sus respectivas optimizaciones en orden y
comunicarlas de alguna manera. Para esta tarea se decidió continuar con la solución planteada en la
defusificación, donde se utilizan arreglos que funcionan a modo de buffers. En total se pueden
distinguir tres tipos de estructuras intermedias:
➔ Arreglos fuzz: Almacenan los valores de salida del fusificador. Se declara un arreglo por
variable de entrada. El tamaño depende de la cantidad de conjuntos difusos asociados a cada
una.
➔ Arreglos pertenencia: Almacenan la salida del motor de reglas. Se declara un arreglo por
cada variable de salida. El tamaño depende de la cantidad de conjuntos difusos asociados a
cada una.
➔ Arreglos valor de salida: Almacenan el valor crisp de cada conjunto difuso de salida. Se
declara un arreglo por cada variable de salida. El tamaño depende de la cantidad de
conjuntos difusos asociados a variable. Son constantes ya que su contenido se conoce en
tiempo de compilación.
Dado que los arreglos se sintetizan como memorias, es necesario aplicar ciertas optimizaciones
que permitan a cada elemento ser accedido de forma independiente y maximizar así el grado de
26
paralelismo, lo que se logra usando la ya mencionada directiva Array Partition. Un beneficio de
utilizar este pragma sobre estructuras globales es que Vivado permite seleccionar sobre cuáles de
las funciones declaradas hará efecto la optimización. Eso significa que en aquellos métodos donde
no sea necesario el acceso simultáneo a algún arreglo, la directiva no tendrá efecto, reduciendo así
el uso de recursos. Por ejemplo, la etapa de fusificación necesita únicamente las variables de
entrada (que recibe por parámetro) y los arreglos fuzz, por lo que estos últimos deben particionarsepara este método. En la etapa de ejecución de reglas se necesita la salida anterior y los arreglos de
pertenencia, por lo que ambos tipos de arreglos deben particionarse para este método. Finalmente
los defusificadores usan la salida del motor de reglas y los arreglos que almacenan los valores de
salida, por lo que en esta etapa se deben particionar los dos últimos tipos de la lista. En resumen:
➔ Los arreglos fuzz se particionan en la etapa de fusificación y en la etapa de evaluación de
reglas.
➔ Los arreglos de pertenencia se particionan en la etapa de evaluación de reglas y en la de
defusificación.
➔ Los arreglos valor de salida se particionan únicamente en la etapa de defusificación.
A su vez, se debe crear un método general que invoca a cada fase del controlador en forma
ordenada. Sobre el mismo se puede aplicar la directiva de Dataflow, permitiendo una comunicación
más fluida y por tanto una mayor eficiencia. Esta optimización permite un paralelismo de “grado
grueso”, es decir, permite la ejecución concurrente de los distintos métodos, a diferencia de Pipeline
que se concentra más bien en el paralelismo sobre instrucción.
3.5 Caso de uso: péndulo invertido
El péndulo invertido es un sistema clásico utilizado por ser un problema relativamente sencillo
cuyas soluciones pueden ser aplicadas al control de mecanismos más complejos, como transporte
aéreo o robots bipedos. Consiste en una varilla (de masa m y longitud L) unida por un extremo al
centro de un carro que se mueve horizontalmente. El prototipo está equipado con sensores para
medir la velocidad, aceleración y ángulo a la que cae la varilla. En base a estas variables se calcula
la fuerza que debe ejercer el movimiento del carro para poder mantener la varilla en equilibrio. Se
asume que la masa del carro y el rozamiento es despreciable, dejando a la gravedad como la única
fuerza externa capaz de desequilibrar el sistema.
Figura 3-2. Esquema del problema de péndulo invertido.
27
Para llevar este sistema a la lógica difusa se plantea el uso de la velocidad angular ( v ) y ángulo
de caída ( α ) como variables de entrada y la fuerza ejercida por el carro ( F ) como salida. Como se
muestra en la Figura 3-2, el ángulo de la varilla se mide con respecto al centro, por lo que el rango
de esta variables es [-180; 180]. En el caso de la fuerza y la velocidad no es posible establecer un
rango de forma tan sencilla, sino que se deben realizar distintas pruebas que permitan determinar el
valor máximo que pueden alcanzar. Se asumió para este ejemplo que dicho valor es treinta, por lo
que su dominio es [-30; 30]. A su vez, todas estas variables se dividieron en los mismso siete
conjuntos difusos: Negativo grande (NL), Negativo medio (NM), Negativo pequeño (NS), Nula (Z),
Positivo pequeño (PS), Positivo medio (PM), Positivo grande (PL). La funciones difusas que
describen a cada uno de estos conjuntos puede verse en la tabla
Conjunto Velocidad Angulo Fuerza
NL FormaZ (-30, -24) FormaZ (-70, -54) Singleton (-30)
NM Triangular (-30, -15, -5) Triangular (-180, -40, -20) Singleton (-18)
NS Triangular (-15, -5, 0) Triangular (-35, -15, 0) Singleton (-5)
Z Triangular (-5, 0, 5) Triangular (-10, 0, 10) Singleton (0)
PS Triangular (0, 5, 15) Triangular (0, 15, 35) Singleton (5)
PM Triangular (5, 15, 30) Triangular (20, 40, 180) Singleton (18)
PL FormaS (24, 30) FormaS (54, 70) Singleton (30)
Tabla 3-5. Funciones difusas originales para problema de péndulo invertido.
Sin embargo, como se mencionó en secciones anteriores, el controlador recibe en la práctica
valores del rango [0; 2n -1], siendo n el tamaño de los conversores A/D. Por esta razón es necesario
encontrar alguna ecuación con la cual obtener un valor estimado de lo que podrían ser los límites
reales de las funciones fusificadoras. Siendo [A; B] el rango real de una variable X y sea n el tamaño
en bits de los conversores A/D, el problema está entonces en encontrar una función tal que dado un
valor del rango [A; B] devuelva su equivalente en el rango [0; 2n -1]. Dicha función tendrá la forma
de una recta, por lo cual el primer paso es buscar su pendiente, tomando los dos puntos extremos de
los rangos anteriormente mostrados:
→ →𝑃 =
𝑦
2 
− 𝑦
1
𝑥
2 
− 𝑥
1
𝑃 = (2
𝑛 − 1)−1
𝐵 − 𝐴 𝑃 = (2
𝑛 − 1) * (𝐵 − 𝐴)−1
Lo siguiente es encontrar la ordenada al origen, pero dado que en todos los casos se da que
-A=B, se puede asumir que la misma es igual al valor medio entre 0 y 2n. En consecuencia, se
obtiene que la fórmula general para traducir los parámetros de las funciones es :
→𝑓(𝑥) = 𝑃 * 𝑥 + 𝑂 𝑓(𝑥) = (2𝑛 − 1) * (𝐵 − 𝐴)−1 * 𝑥 + (2𝑛 − 1) * 0. 5
Si ahora se reemplazan las variables A y B con los valores indicados por los respectivos rangos
de cada variable, y asumiendo que los conversores tienen un ancho de 8 bits, se obtiene que:
Para la variable ángulo:
𝑓(𝑥) = (0, 70) * 𝑥 + (127, 5)
Para las variables fuerza y velocidad
𝑓(𝑥) = (4, 25) * 𝑥 + (127, 5)
28
En base a ellas, se redefinen los parámetros originales de las funciones. Los resultados obtenidos
pueden verse en la tabla 3-6. Es importante recordar que estos valores son únicamente estimativos,
y solo muestran una opción viable para que el usuario adapte su problema. A partir de este punto, el
objetivo es que la herramienta generadora permita a los usuarios calibrar su controlador de forma
rápida. En otras palabras, puede que los valores aquí mostrados sean los correctos, pueden que
necesiten ajustes o incluso que sean descartados completamente, la decisión final depende
únicamente del usuario.
Conjunto Velocidad Angulo Fuerza
NL FormaZ (25, 64) FormaZ (54, 78) Singleton (0)
NM Triangular (0, 64, 106) Triangular (0, 99, 113) Singleton (51)
NS Triangular (64, 106, 127) Triangular (103, 117, 127) Singleton (106)
Z Triangular (106, 127, 149) Triangular (120, 127, 132) Singleton (127)
PS Triangular (127, 149, 191) Triangular (127, 138, 152) Singleton (149)
PM Triangular(149, 191, 255) Triangular (142, 156, 255) Singleton (204)
PL FormaS (191, 229) FormaS (166, 177) Singleton (255)
Tabla 3-6. Funciones difusas convertidas para problema de péndulo invertido.
Con estos nuevos parámetros definidos, solo resta implementar el fusificador, tal como se indicó
en la sección 3.1 pero sin las mejoras. El siguiente paso es la construcción del motor de reglas, cuya
base puede verse representada como una matriz en la tabla 3-6. Es importante mencionar que se
utilizó una matriz para mostrar las reglas de forma ordenada, pero en la práctica se utilizaron
secuencias de if-then.
Tabla 3-7
v
NL NM NS Z PS PM PL
ɑ
NL PL PL PM PM PS PS Z
NM PL PM PM PS PS Z NS
NS PM PM PS PS Z NS NS
Z PM PS PS Z NS NS NM
PS PS PS Z NS NS NM NM
PM PS Z NS NS NM NM NL
PL Z NS NS NM NM NL NL
Tabla 3-7. Matriz de reglas para problema de péndulo invertido.
Finalmente, se implementó un defusificador Centroide, utilizando las mejores directivas
previamente encontradas, y se generó la función principal, donde se invoca cada una de las etapas
en forma ordenada. Con el controlador terminado, el algoritmo se simuló en alto nivel, tanto
utilizando Vivado HLS como con un IDE específico de C++, para verificar que los resultados
obtenidos en el Test Bench eran correctos. Luego de haber comprobado que los valores coinciden,
se continúo con la síntesis del algoritmo, sin directivas ni mejoras y luego con la versión
optimizada. En ambos casos se utilizó la placa xc7z020clg400-1 y Vivado HLS versión 2020.1.
Versión Clk (ns)
Latencia (ciclos) Intervalo (ciclos)
BRAM DSP FF LUT
Min Max Min Max
Sin mejoras 8.621 283 283 283 283 8 80 19036 30877
Con mejoras 8.581 28 28 1 1 0 3 1713 4721
Tabla 3-8. Resultados obtenidos luego de síntesis del controlador para péndulo invertido.
Como se puede observar, en la versión sin mejoras la latencia de 283 ciclos implica que se deben
esperar aproximadamente 2830 ns para obtener un resultado, mientras que un intervalo de 283
ciclos implica que se deben esperar 2830 ns para recibir el próximo resultado.Mientras tanto, en la
versión optimizada se puede apreciar una mejora del 1000% en la latencia y del 2800% para el
intervalo, además de una notable reducción en el espacio ocupado por el controlador, a pesar de que
las directivas provocan un mayor uso de recursos. Como ya se mencionó, si bien el uso de variables
con ancho fijo provoca que los valores sean truncados y se pierda precisión, observando los
resultados de la síntesis se hace evidente que las mejoras obtenidas justifican esta pequeña falencia.
Sin contar además que este truncamiento se realiza durante la conversión y no es posible de
solucionar por software.
Con base en los reportes del controlador optimizado, es posible realizar un análisis de
factibilidad del algoritmo. Se sabe que el tiempo de espera entre cada batch de datos estará limitado
por el tiempo de lectura de sensores y el retardo que introducen los conversores A/D, por lo que el
controlador deberá encontrarse debajo de esa cota para afirmar que es posible llevarlo a la práctica.
Si se asume que el retardo de lectura es despreciable, entonces la cota depende únicamente del
tiempo de conversión, el cual se encuentra cerca de 1µs, considerando los conversores A/D de 12
bits de los dispositivos modernos de Xilinx (XDAC1) que operan a 1 MSPS (million samples per
second). Mientras tanto, el controlador es capaz de devolver un resultado en 0.028 µs, considerando
que se estableció un periodo de clock de 10 ns durante la síntesis. Esto implica que las lecturas se
procesarán en el orden correcto, ya que es el controlador quien debe esperar por nuevas entradas y
no viceversa.
1 https://www.xilinx.com/products/intellectual-property/axi_xadc.html
30
CAPÍTULO IV: GENERADOR DE FLC
En este capítulo se discute el desarrollo del generador de FLC, desde
su diseño hasta su implementación. El proceso se divide en tres
partes. La primera es la abstracción de componentes, donde se
analiza la información mínima necesaria para definir los
controladores. Luego está la creación de un lenguaje que permita
utilizar la herramienta de forma ágil. Finalmente se encuentra la
implementación propiamente dicha del generador, donde se explica el
diseño y las circunstancias que llevaron a elegirlo.
4.1 Abstracción de componentes y diseño general
Con los datos obtenidos durante la optimización de controladores difusos en Vivado HLS y las
pruebas realizadas usando el problema del péndulo invertido, se comenzó con la creación de la
herramienta generadora de FLC’s. Como se vió en el capítulo anterior, existe una gran variedad de
algoritmos para cada etapa y por tanto es prioritario acotar los límites de la investigación para esta
primera versión del programa. Como consecuencia, solo se soportan fusificadores del tipo
Triangular, Forma S, Forma Z, Trapezoidal y Singleton; densificadores de tipo Centroide y
Mandami como único posible método de evaluación de reglas, lo que se corresponde con los
algoritmos procesados durante el capítulo tres.
La siguiente tarea es abstraer los componentes, de forma tal que al usuario se le solicite la
mínima cantidad de información necesaria para definir un controlador. El razonamiento es que,
cuantos menos datos sean necesarios, más rápidos y ágiles serán los desarrollos. Comenzando por
las entradas y salidas del sistema, se puede afirmar que para cada variable es necesario definir en
primera instancia su tipo (entrada o salida), y luego los conjuntos difusos que las conforman. Por
ejemplo, si se quiere definir la variable Temperatura, es necesario establecer que estará formada por
los conjuntos Frío, Templado y Cálido. A su vez, cada uno de estos conjuntos difusos puede
representarse como la invocación de una función, donde su nombre indicaría el tipo de algoritmo
utilizado para la fusificación y sus parámetros establecen el dominio. Es importante tener en cuenta
que tanto para variables de entrada como de salida es necesario definir una función difusa, por más
que estas últimas no se deban fusificar. Para el método de evaluación únicamente es necesario
establecer un algoritmo y una base de reglas, las cuales se forman a partir de una combinación de
las variables de entrada que devuelven una serie de variables de salida como resultado. Finalmente,
para el proceso de defusificación restaría definir, para cada variable de salida, que método
defusificador utilizara.
Observando esta descripción, es posible distinguir cuatro partes distintas: una para declaración
de variables, otra para declaración de conjuntos difusos, otra para la evaluación de reglas y
finalmente una para establecer el defusificador. El problema ahora está en decidir de qué manera se
ingresarán estos datos. Inicialmente se ponderó la posibilidad de utilizar una interfaz gráfica, lo cual
proveería a la herramienta de una gran usabilidad, sin embargo, el proceso de creación de reglas
podría ser bastante complejo. Por ejemplo, una solución podría ser generar una matriz, donde en
cada casilla el usuario simplemente elija alguno de los conjuntos difusos de salida definidos, pero
ya que existe la posibilidad de que haya reglas repetidas o sin definir, la matriz sería una
representación que limita las capacidades de la herramienta. Otra posibilidad sería utilizar una lista,
31
donde las reglas se ingresen una a una, pero el proceso sería tedioso y lento, lo cual contradice el
objetivo principal del proyecto. Por esta razón, la idea de utilizar una interfaz gráfica se descartó, y
en su lugar se propuso la creación de un pseudo-lenguaje, que permita a los usuarios generar una
descripción del controlador de la manera que les resulte más cómoda.
Luego de establecer que la entrada de la herramienta es una descripción basada en un
pseudolenguaje propio, y sabiendo que la salida consta de un programa escrito en C++, se hace
evidente que el diseño del generador será muy similar al de un compilador, donde se distinguen al
menos tres componentes: Analizador Léxico, Analizador Sintactico y Generador de Código. Podría
considerarse también algún optimizador, pero dado que la salida deberá implementar las directivas
mostradas en el capítulo tres, esta tarea queda relegada al generador de código. La arquitectura final
del controlador sería la siguiente:
Figura 4-1. Arquitectura del generador de FLC.
Para su implementación se plantea el uso de YACC [21], porque permite agilizar el desarrollo y
porque se cuenta con experiencia previa en su uso, por lo que será el analizador sintáctico el que
controle todo el proceso de generación. Inicialmente se solicita tokens al analizador léxico, hasta
poder formar una estructura sintáctica de la cual se podrá extraer información sobre el controlador.
Estos datos se envían al traductor intermedio, para que los almacene en la manera que sea
conveniente. Una vez el código de entrada es completamente procesado, se envía la estructura
intermedia al traductor de salida, quien generará el código en C++ correspondiente a los datos
contenidos. En la figura 4-2 se puede observar el funcionamiento de la herramienta de manera
abstracta.
Figura 4-2. Flujo de funcionamiento de la herramienta generadora.
32
En este punto es necesario mencionar los atributos de calidad que guían la implementación del
generador de FLC. En primera instancia, dada la reducida cantidad de algoritmos disponibles en
esta primera versión de la herramienta, y tomando en consideración que la incorporación de nuevas
opciones son parte del trabajo futuro, la escalabilidad se convierte en un aspecto primordial. Se
espera también que estos cambios puedan ser aplicados por cualquier persona, por lo que además de
escalable debe ser modificable, y para ello usar herramientas estandarizadas como YACC es de gran
utilidad. Otra propiedad deseada es la flexibilidad, y con este fin se debe dotar al generador de las
funcionalidades necesarias para que se adapte de la mejor manera a la mayor cantidad de
problemas. Por último, el lenguaje usado para describir los controladores debe ser cómodo de
utilizar por el usuario, por lo tanto la

Continuar navegando