Comienzos con nMigen
![]() |
iCE40UP5K-B-EVN |
El propósito de este artículo es compartir el código de los ejercicios realizados en mi comienzo en este lenguaje FHDL para FPGAs. Antes de nada, indicar el motivo de la elección de nMigen y la experiencia previa que tenía con las FPGAs.
El proyecto final de estudios que realicé fue con una FPGA, debido a mi curiosidad y el potencial de procesamiento en paralelo que ofrecen. Pero como se suele decir, empecé de la manera difícil "the hard way", libros sobre lógica digital, lenguaje VHDL, herramientas del fabricante con infinidad de opciones y gigantescas en tamaño (más de 20 GB), había que usar Windows, etc...
Realicé desde pequeños ejercicios hasta utilizar un "sofcore" conectado a varios módulos que leían sensores, encoders en cuadratura, antirrebotes para las entradas, y controlaba un par de motores todo por hardware. Las ordenes y comunicación con el ordenador las realizaba mediante el "softcore". Fueron unos pocos meses, pero se investigó y leyó bastante. Se probó a diseñar mediante VHDL y gráficamente mediante esquemas. Y tras entregar el proyecto, quedó claro que el desarrollo de hardware es difícil, y el tiempo que pasaba cada vez que realizaba un pequeño cambio y tenia que sintetizar todo era "apreciable", sin contar con que la FPGA que usaba tenia infinidad de pines y 3 o 4 fuentes de alimentación diferentes para funcionar. Con lo que se descartó por completo tratar de usarlas en proyectos personales, o realizar una PCB que tuviera una FPGA.
Han pasado unos pocos años y gracias a las herramientas FLOSS (Free/Libre Open Source Software) es posible trabajar con las FPGAs en sistemas GNU/Linux, ocupan poco tamaño y son mucho más rápidas a la hora de sintetizar. Para más información leer sobre el proyecto IceStorm y Symbiflow, que hacen posible utilizar las FPGAs ICE 40 y ECP5 de Lattice sin ningún software del fabricante.
Esto junto con la proliferación de PCBs con las FPGAs ICE40 UP5K, me hizo pensar que ya si que podría utilizar una si hiciera falta en un proyecto personal a modo de hobby. La FPGA en cuestión es muy pequeña, con solo 48 pines en la versión SG48, con un consumo muy bajo y solo necesita 2 voltajes de alimentación diferentes para funcionar, habiendo reguladores que convierten directamente los 5V de entrada (USB por ejemplo) en 3.3V y 1.2V que necesita.
Sus características principales son las siguientes:
- 5280 LUTS + Flip-Flop.
- 120 Kbits de memoria EBR.
- 1024 Kbits de memoria SPRAM.
- Memoria de configuración no volátil NVCM.
- 1 bloque PLL en hardware.
- 8 bloques DSP en hardware.
- 2 unidades I2C en hardware, con 2 pines capaces de I3C.
- 2 unidades SPI en hardware.
- 1 oscilador interno a 48 Mhz.
- 1 oscilador interno a 10 Khz.
- Driver RGB por hardware, hasta 24mA por pin directamente.
- 1 bloque PWM en hardware.
- 2 unidades de delay de 50ns por hardware.
- 2 unidades de filtro de 50ns por hardware.
- Empaquetado QFN de 48 pines (0.5mm x 7mm x 7mm).
- 39 pines de entrada y salida.
- Consumo de hasta 100 uA en reposo.
Tutorial de Verilog
Las herramientas libres utilizan Verilog, por lo que es conveniente conocer un poco el lenguaje para ver posteriormente el código para depurar errores que genera nMigen o poder reutilizar módulos de otras personas escritos en Verilog. Para ello recomiendo encarecidamente el tutorial de Juan González Gómez (Obijuan).Durante la realización de este tutorial se han creado unas tareas en VS Code para no tener que escribir tantos comandos en la consola. También se ha utilizado el plugin para Verilog.
En el repositorio se encuentran unos pocos ejercicios del tutorial indicado anteriormente junto con la configuración.
Destacar que en el ejemplo del prescaler se ha instanciado el driver RGB que incluye la FPGA, lo que se denomina "hard IP".
A continuación se muestran unas capturas de pantalla con las tareas que se pueden elegir:
![]() |
Tareas para Verilog |
A destacar las tareas que engloban a otras:
- Simulate(ALL): ejecuta en orden las tareas de compilación, ejecución y visualización de la simulación.
- Synthesis(ALL): ejecuta en orden las tareas de síntesis, colocación, enrutado y generación del bitstream.
- Syntesis&Program: ejecuta lo mismo que la tarea anterior y además programa la FPGA.
- View schematics: ejecuta primero la tarea de generación del esquema y después lo muestra. En este caso mediante una imagen svg.
![]() |
Configuración de tareas |
Al comienzo del archivo task.json se pueden configurar algunos parámetros, al igual que la versión para nMigen: El nombre del archivo, que no es necesario cambiar, ya que se incluye un archivo de tareas por cada ejemplo; el modelo de fpga, el empaquetado de la misma y el programa para visualizar los archivos svg.
![]() |
Esquema de un contador de 26 bits en Verilog |
Este es el esquema del ejercicio del contador de 26 bits generado por la tarea View schematics.
nMigen
Dado que el diseño con los lenguajes de modelado tradicionales es muy lento y propenso a errores, algunos lo comparan con programar en ensamblador (aunque hay que tener en cuenta que no se está programando, se está diseñando), han surgido otros lenguajes. Y muchos de estos lenguajes coinciden en abandonar el paradigma conducido por eventos que se llevaba utilizando hasta el momento.Estos nuevos lenguajes tratan de elevar el nivel de abstracción y hacer posible el diseño de sistemas digitales complejos de forma muchos más fácil, y deja en manos de los generadores de código el producir módulos y librerías reutilizables y libres de los errores comunes que se han observado durante el diseño de sistemas en Verilog o VHDL a lo largo de los años. Estos generadores crean al final código de representación intermedia o Verilog o VHDL. Uno de ellos es Chisel, que utiliza el lenguaje Scala y se ha usado para diseñar las CPUs en la universidad de Berkeley, destacando la arquitectura RISC-V. También lo utilizan los ingenieros de SiFive a la hora de diseñar el hardware. Por lo que se ha podido investigar, este parece el más maduro, pero su curva de aprendizaje es alta y es más adecuado para quienes trabajan a diario en el diseño de hardware.
Otro de los lenguajes o librerías es nMigen, que está basada en Python y según ellos es un lenguaje FHDL (Fragmented Hardware Description Language), en el cual se separan las declaraciones en dos grupos: síncronas y combinacionales, las reglas aritméticas son las mismas que en las matemáticas con enteros, y permite generar y simular la lógica en Python.
nMigen es el descendiente de Migen, el cual se ha usado en producción durante años y permite crear diseños complejos de forma mucho más fácil y rápida. Recomiendo ver el siguiente video de una charla sobre como se ha implementado una PCB para la captura de video en conferencias y emisión por streaming, incluyendo la señal HDMI de los proyectores, pasándola a USB. En el proyecto del vídeo se ha usado Litex, que contiene componentes diseñados con Migen y permite crear de forma rápida SoC, incluyendo componentes como: cores con o sin CPU, buses y streams (Wishbone, AXI, Avalon-ST), cores comunes con RAM, ROM, Timer, UART, etc... , así como DRAM, PCIe, Ethernet, SATA, etc...
Esto permite crear sistemas que antes no eran posibles(a nivel aficionado o uso esporádico), bien por los conocimientos necesarios o por el tiempo que llevaría realizarlos.
Debido a todo lo anterior decidí usar nMigen, que intenta solventar los problemas que se han tenido y tienen mientras se trabaja con Migen. Aunque hay que decir que nMigen está en estado muy prematuro, la versión disponible es la 0.1 y en la rama de desarrollo están trabajando en la 0.2, en la que piensan arreglar problemas con el simulador. También quieren mejorar el código generado en Verilog para que sea más fácil de leer.
Las únicas fuentes de información que se han encontrado son las siguientes:
Por lo que entre lo dicho anteriormente y que gran parte del código no está comentado actualmente, es un poco complicado empezar. Se ha tenido que leer mucho código fuente y realizar pruebas para tratar de comprender como trabajan ciertas funciones o como conectar los diseños con el hardware real. Por ello comparto el código con ejercicios que funcionan en la placa de desarrollo.
A la hora de instalar el software necesario consultar las instrucciones de Robert Baruch y las de SymbiYosys.
Verificación formal
nMigen soporta la verificación formal, al igual que verilog gracias a SymbiYosys. Solo soporta unas pocas declaraciones formales en comparación con SystemVerilog, pero son suficientes.
La verificación formal trata de probar que un algoritmo es correcto tratando de hacer que no cumpla con las especificaciones mediante métodos formales y matemáticas. De forma muy simple, intenta ejercitar las entradas, de un módulo por ejemplo, para encontrar un caso en el que no se cumplan las especificaciones.
Se utilizan las siguientes declaraciones:
- Assert(): para indicar que el resultado de una expresión tiene que ser verdadera.
- Assume(): para asumir el valor de una expresión, de forma que se excluyan casos que no se quieran probar o que se sepa que no van a ocurrir.
- Past(): devuelve el valor de una expresión un número de ciclos en el pasado.
- Stable(): devuelve verdadero si la expresión no ha cambiado con respecto al ciclo de reloj anterior.
- Rose(): devuelve verdadero si la señal se ha activado durante el flanco positivo del último ciclo de reloj.
- Fell(): igual que la anterior pero para el flanco de bajada.
En el repositorio hay algunos ejemplos, los archivos terminados en _formal.py, son muy simples pero dan una idea de como utilizar las declaraciones anteriores.
Ejercicios en nMigen
En la carpeta del repositorio se encuentran los archivos del tutorial de Obijuan realizados con nMigen. Los archivos tienen el mismo nombre que en el tutorial pero acabados en .py, _tb.py y _formal.py, para módulo, testbench y verificación formal respectivamente. La idea es que se siga el tutorial, donde se explican los ejercicios, y se compare el código Verilog con el de nMigen.Decir que el código no sigue las convención pep8, ni está documentado correctamente, solo es el código que se ha realizado durante los ejercicios con el fin de aprender nMigen. Para muestra de un código Python comentado según Sphinx y que sigue el pep8, mirar un proyecto terminado, por ejemplo PyControlPanel.
En la carpeta deberían existir 3 subcarpetas:
- Src: contiene los archivos Python.
- Build: contiene los archivos generados por nMigen.
- Output: contiene los archivos generados por las tareas de VS Code.
En la carpeta de configuración de VS Code (.vscode) se encuentra el archivo de tareas task.json.
Al pulsar F1 se abrirá un dialogo en el que tenemos que buscar el lanzador de tareas.
![]() |
Lanzador de tareas |
Esto leerá el archivo y nos mostrará las tareas que se han definido en él.
![]() |
Tareas para nMigen |
De esta forma no tenemos que escribir tantos comandos en la consola. Las tareas que empiezan por __(doble barra baja) no hace falta usarlas ya que las llaman internamente el resto de tareas.
La descripción de las tareas es la siguiente:
- nMigen Build: ejecuta el archivo del ejercicio seleccionado para su construcción, realizando también la programación de la FPGA.
- nMigen Simulate: ejecuta el archivo de testbench del ejercicio seleccionado.
- SysbiYosys: ejecuta el archivo de verificación formal del ejercicio seleccionado.
- Timming Analysis: muestra el análisis de tiempos generado por Yosis cuando se ejecutó nMigen Build.
- View schematic: genera y muestra un pdf con un esquema gráfico que representa el módulo seleccionado. Es necesario tener dot instalado en el sistema.
- View simulation: muestra el testbench generado anteriormente mediante Gtkwave.
- View SymbiYosys --- 1st generated trace: muestra la primera traza generada por la verificación formal en Gtkwave. Distinguiendo entre las generadas por BMC, COVER o PROVE.
![]() |
Configurando tareas |
Podemos configurar varios parámetros en el apartado env del archivo de tareas: en filename escribimos el nombre del archivo Python sin extensión y todas las tareas estarán referidas al mismo; en fpga_arch elegimos el modelo de la fpga a usar, en fpga_package el empaquetado del chip, en pdf_viewer el visor a utilizar para los pdfs y en pythonPath la ruta al interprete Python a utilizar por las tareas.
Los parámetros relacionados con la fpga se pasan a Yosis u otros comandos del proyecto IceStorm.
![]() |
Configuración para depurado |
También se encuentra un archivo launch.json que se puede utilizar a la hora de depurar código Python. Solo basta con cambiar el nombre (name) y la ruta al archivo (program).
Archivos a destacar
Existen algunos archivos aparte de los propios ejercicios que hay que mencionar:- ice40_up5k_b_env.py: este archivo añade soporte para la placa utilizada a nMigen, ya que no se incluye ninguno en el repositorio oficial.
- masm.py: pasa el código ensamblador a código máquina para el último ejercicio: microbio. Realizado por Obijuan y modificado ligeramente para no añadir comentarios al lado de las instrucciones.
- verilog_module.py: es un ejemplo de como instanciar un módulo escrito en Verilog y usarlo en nMigen.
Capturas
Todos los ejercicios, a excepción de los que utilizan el zumbador, han sido probados en la placa junto con un analizador lógico. Y las simulaciones coinciden con las mostradas en el tutorial.A continuación algunas capturas de ejercicios:
![]() |
baudtx2.py |
![]() |
fsmtx.py |
![]() |
buffer.py |
![]() |
fsmtx_tb.py |
![]() |
scicad1_tb.py |
![]() |
Contador nMigen |
![]() |
Microbio nMigen |
Conclusiones
Después de realizar los ejercicios y por lo que se ha visto, está claro que el diseño es más rápido que con Verilog o VHDL, pero nMiguen todavía está en una fase de desarrollo muy temprana como se ha dicho anteriormente (V 0.1), falta mucha documentación y hay algunos problemas con el simulador. Habrá que estar atento para ver como avanza el desarrollo.Si uno quiere dedicarse al diseño de hardware de forma profesional, el más recomendable de los "nuevos" lenguajes es Chisel, pero para un uso esporádico, pequeños proyectos, o no muy complicados, recomendaría usar Migen o MyHDL. Este último utiliza también Python, pero sigue el tradicional modelo basado en eventos, por lo que sería como diseñar en Verilog pero usando Python. Desde luego que es mucho más ameno y rápido que Verilog o VHDL.
El motivo de volver a las FPGAs es poder realizar diseños sencillos pero prácticos en los proyectos, como poder disponer de múltiples decodificadores de encoders en cuadratura, o puertos UART o SPI adicionales, leer sensores a alta frecuencia y mediante buffers pasar los datos a un microcontrolador, realizar partes de la lógica en hardware (siguen funcionando si el micro se bloquea y pueden controlar o monitorizar aspectos relacionados con la seguridad), circuitos de reset para otros integrados, etc...
Espero que sirva de ayuda para el que quiera empezar con nMigen y no sepa por donde.
Como siempre, dar las gracias a quienes se han tomado el tiempo de leer este artículo y recordar que el código fuente se encuentra en el repositorio.
0 comentarios: