Si alguna vez has intentado construir un microprocesador desde cero, sabes que es un campo de batalla lleno de hazards, control de memoria y manejo de instrucciones. Un verdadero caos digital. En medio de ese embrollo, me topé con una herramienta que me cambió la forma de ver el desarrollo en FPGA: LiteX.

¿Qué es LiteX?

LiteX es un framework para el diseño de SoCs (System on Chip) en FPGA, que transforma el proceso en algo similar a armar bloques de LEGO: eliges un procesador, añades controladores, memorias, periféricos, y ¡listo!, todo encaja de manera elegante y organizada. Lo más impresionante es que la integración de un procesador RISC-V se realiza en minutos, literalmente. En un entorno tradicional, esto podría llevar semanas o incluso meses de trabajo.

Instalación de LiteX

Para empezar, necesitas un entorno Linux (funciona en Windows, pero seamos sinceros: en Linux es mucho más fluido). Además, necesitarás Python y herramientas de síntesis para tu FPGA. Si estás usando una Tang Nano 9K, puedes aprovechar Apicula, una herramienta open-source para esta familia de FPGAs.

Pasos de instalación

La instalación es directa: clonas el repositorio y ejecutas el instalador:

wget https://raw.githubusercontent.com/enjoy-digital/litex/master/litex_setup.py
chmod +x litex_setup.py
./litex_setup.py --init --install --user --config=standard

Con eso, ya tienes todo listo para empezar a construir tu SoC. LiteX se encarga de todas las dependencias; no hay que preocuparse por paquetes faltantes ni conflictos. Si tienes algun problema te recomiendo leer la documentacion desde la repo oficial de LiteX

Con LiteX instalado, es hora de crear nuestro primer SoC. Para este ejemplo, usaremos la Tang Nano 9K, una FPGA pequeña pero poderosa. Dentro del directorio litex-boards hay dos tipos de archivos clave:

  • Platform: Define los pines y periféricos físicos de la FPGA.
  • Target: Define el SoC como tal, es decir, los módulos y periféricos que vamos a integrar.

Para construir el SoC, creamos un archivo llamado tangnano9k.py en el directorio de targets y lo ejecutamos:

python3 tangnano9k.py --build

Esto genera el bitstream y los archivos de configuración en la carpeta build/. Para cargarlo en la FPGA:

python3 tangnano9k.py --load

Y ¡listo! El LED de usuario debería empezar a parpadear.

Blinking LED

Además, se habilita una consola UART en `/dev/ttyUSB0` a 115200 baudios:
litex_term /dev/ttyUSB0 --speed 115200

Agregando un Periférico GPIO

Hasta aquí, todo bien, pero un SoC sin periféricos útiles es como un auto sin ruedas. Vamos a integrar un GPIO en nuestro diseño.

Los dos archivos clave son:

  • platform.py: Define los pines físicos de la FPGA.
  • target.py: Define el SoC y los periféricos.

Definiendo el GPIO en platform.py

("gpio_tristate", 0,
    Pins("25 26 27 28 29 30 33 34"),
    IOStandard("LVCMOS33")
),

En este caso, estamos definiendo un GPIO de 8 bits. conectado a los pines 25 a 34 de la FPGA. Puedes cambiar los pines según tu diseño.

Para este caso, hay un diferencia entre los pines logicos y los pines fisicos, como se definieron; el pin fisico 25 es el pin logico 0, el 26 es el 1, y asi sucesivamente.

Tang nano

Instanciando el GPIO en target.py

self.gpio = GPIOTristate(platform.request("gpio_tristate"))
self.add_csr("gpio")

Aquí estamos instanciando el GPIO y añadiéndolo al SoC. GPIOTristate es una clase que maneja la lógica de entrada/salida del GPIO. La función add_csr añade el GPIO al espacio de registros del SoC, permitiendo su acceso desde el código C.

Controlando el GPIO desde C

Ya agregado el GPIO, es hora de controlarlo desde C. Para ello, LiteX nos proporciona un conjunto de funciones para acceder a los registros del GPIO. Aquí te muestro cómo hacerlo:

Funciones de Acceso GPIO

/* GPIO Access Functions */
static inline uint32_t gpio_oe_read(void) {
	return csr_read_simple((CSR_BASE + 0x0L));
}

static inline void gpio_oe_write(uint32_t v) {
	csr_write_simple(v, (CSR_BASE + 0x0L));
}

static inline uint32_t gpio_in_read(void) {
	return csr_read_simple((CSR_BASE + 0x4L));
}

static inline uint32_t gpio_out_read(void) {
	return csr_read_simple((CSR_BASE + 0x8L));
}

static inline void gpio_out_write(uint32_t v) {
	csr_write_simple(v, (CSR_BASE + 0x8L));
}

Ejemplo de uso en C:

Este es un ejemplo de cómo activar un bit en el GPIO. La función set_gpio_bit toma un número de bit (0-31) y lo activa en el registro de salida del GPIO, usando las funciones de acceso definidas anteriormente.

void set_gpio_bit(uint8_t bit) {
    if (bit > 31) {
        printf("Error: El bit debe estar entre 0 y 31.\n");
        return;
    }

    uint32_t oe_value = gpio_oe_read();
    oe_value |= (1 << bit);   // Activa el bit en el registro OE
    gpio_oe_write(oe_value);

    uint32_t out_value = gpio_out_read();
    out_value |= (1 << bit);  // Activa el bit en el registro de salida
    gpio_out_write(out_value);

    printf("Bit %d activado. Valor actual del GPIO_OUT: 0x%08X\n", bit, gpio_out_read());
}

Con esto y demas funciones de demo que he creado en la repo de este tutorial, puedes controlar el GPIO desde tu código C. Puedes encontrar el código completo en mi repositorio de GitHub

En este ejemplo hay un par de funciones como blink, que puede usarse para parpadera un led conectado a un pin del GPIO, y un par de funciones para leer el estado de los pines.

gpio

Conclusión

LiteX revoluciona la forma en que diseñamos SoCs en FPGA, permitiéndonos ensamblar periféricos y procesadores como si fueran piezas de LEGO. Con unas pocas líneas de código y el poder de LiteX, podemos controlar GPIOs, UART, e incluso correr código en C en un RISC-V diseñado por nosotros mismos.

¿Listo para crear un SoC con más periféricos y comenzar a explorar más allá de los ejemplos básicos? 🚀