Descarga la aplicación para disfrutar aún más
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
Compartir