2020/11/05

PYNQ con dos seriales en FPGA

Introducción

 

Mis requistos vienen a ser algo así como:

Implementar el hardware necesario en la FPGA tal que un programa ejecutándose en el procesador pueda comunicarse via serial. Los caracteres recibidos serán enviados por un acelerador criptográfico y los por éste generados devueltos por la conexión serial.

Debe haber además otro puerto serial donde el acelerado pueda escribir y una salida VGA con un patrón de ajuste.

Para ello utilizaré como hardware un SOC ZYNQ-7020, en particular una placa PYNQ, que tiene dos procesadores y una FPGA y como SDK Xilinx Vivado 2018.2. Mi primer contacto con esta placa fue https://seguridad-agile.blogspot.com/2019/04/python-productivity-for-zynq.html y en realidad no la estoy usadon como PYNQ propiamente dicha sino como un ZYNQ 7020. Me hubiese gustado usar al Parallella pero ando con el tiempo muy justo y aunque despues al leer lo que sigue te parezca que la tengo re clara y una gran soltura con el tema, la verdad es que me cuesta horrores y no quiero la dificultad adicional de lidiar con una placa sin soporte.

Esta experiencia la segmentaré en varias entregas:

Primera entrega, son dos proyectos independientes, para ejercitar usar IPs existentes:

  • Serial, que el programa usara para hacer eco simple.
  • Doble serial, el programa pasará caracteres de uno a otro.


Siguientes entregas, veremos cuántas y si se agrupan o no:

  • VGA, no hay IP,  tengo que portar lo que ya hice en otro lado.
  • Acelerador, recibe una clave, luego cifra cada caracter.
  • Integración, todo lo anterior funcionando pero con el segundo serial desconectado.

Y finalmente la razón de todo esto:

  • Ataque, el acelerador publicará en el puerto serial la clave o el texto plano y eventualmente integraré esa señal dentro del VGA.



La idea de fonto está en el estudio de un libro y me baso en los ejercicios del libro The Zynq Book, tal cual lo practicado. En aquella ocasión contaba con que quien leyera estuviera seguiendo los pasos del libro en su Vivado, así que fueron solo unas notas de acompañamiento. En esta oportundidad, detallaré mucho más, además mostrando donde hay fallas o limitaciones de la herramienta, ya sean reales o producidas por mi ignorancia.

 

Estoy asumiendo que algo sabés. Sólo voy a poner las capturas de pantalla indispensables o significativas.

 

Serial

 

La idea es generar un BSP con las CPUs, el puerto serial y las conexiones y componentes de soporte.

 

Cargar la placa a Vivado

  • En el primer ejercicio están las instrucciones para agregar la placa PYNQ a Vivado

Crear el proyecto

  • Create Project
  • RTL Project
  • Target Language Verilog
  • Skip add files
  • Skip add constraints
  • Default Part
  • Boards
  • Seleccionar pynq-z2
  • Agregar el xdc, que es el mapeo de los pines a la FPGA
  • Y asignarle los nombres rx y tx a algún pin que quieras, yo a ar[0] y ar[1]

 

xdc
xdc

 

Crear el hardware

 

  • IP INTEGRATOR
    • Create Block Design, poner un nombre apropiado
    • Agregar componentes con Add IP
      • ZYNQ7 Processing System
      • Run Block Automation

Block Automation
Block Automation

 

      • Uartlite 
      • Run Connection Automation
        • te va a agregar lo que haga falta para que funcione
        • pero sólo pedile S_AXI, basta de automation

 

Connection Automation
Connection Automation

 

 

        •  Te queda así, podés mover las cosas de lugar si querés

Los componentes interconectados
Los componentes interconectados

 

 

 

    •  Conectar al mundo exterior
      • Hacele click UART en axi_uartlite_0 para que se descomponga en rt y tx

 

Detalle de puerto UART
Detalle de puerto UART

      • Con botón derecho en rx y tx, haceles "make external"
      • Probablemente les ponga nombre rx_0 y tx_0
      • Corregí en el xdc
    • File -> Save Block Design
    • Tools -> Validate Design
    • Windows -> Sources -> botón derecho -> Create HDL Wrapper

 

HDL Wrapper
HDL Wrapper

 

 

    • Y finalmente vamos a sintetizar, implementar y generar el bitstream como siempre
      • IMPLEMENTATION -> Generate Bitstream 
        • Dispara todo el proceso
      • Mejor andate lejos de la máquina pues esto consume un montón de CPU y si le dás al Candy Crush te podés quedar sin vidas.
    • Podés "Open Implemented Design para ver los dibujitos"

 

Implemented Design
Implemented Design


¿Cuál es la situación actual?

 

Tenemos un bitstream que define un puerto serial, lo conecta por un lado a dos pines del mundo exterior y por el otro, vía un AXI Interconnect al PS (Processing System).

 

Ahora vamos a por el sofware


  • File -> Export -> Export Hardware -> include bitstream
  • File -> Launch SDK
    • Esto abre una nueva ventana, es otro programa
  • File-> new -> Application Project
    • Acá podrías elegir freertos, linux o standalone
      • standalone
    • template -> empty
    • Vas a tener system.mss
    • te ofrece un montón de "Import Examples"
      • para axi_uartlite_0
        • low level example
    • te recomiendo que veas todos los de uart
    • los que tienen interrupciones tienen fallas de inclusión que no quiero diagnosticar
    • En este punto podés hacer múltiples proyectos de software sobre el mismo hardware

 

El primer programa, echo


Voy a usar el ejemplo low level como base, poníendole en el main():

 

    u8 data;;
    xil_printf("start\r\n");
    for(data= 32; data < 60; ++data) {
       XUartLite_SendByte(UARTLITE_BASEADDR, data);
    }
    while (1) {
        data =  XUartLite_RecvByte(UARTLITE_BASEADDR);
        XUartLite_SendByte(UARTLITE_BASEADDR, data);
        xil_printf("Echoing %d\r\n", data);
    }

Para ver los xil_printf hay que conectarse a la terminal:


Abrir diálogo con el más verde
Abrir diálogo con el más verde

Elegir puerto
Elegir puerto

Listo
Listo


Pero antes, hay que mandar el bitstream, como siempre desde Vivado.


  • PROGRAM AND DEBUG
    • Open Hardware Manager
      • Open Target
        • Auto connect
      • Program Device


Ahora tenemos el hardware listo, mientras no reiniciemos la placa, no hace falta reprogramar, volvamos al SDK.


Elegir el proyecto -> botón derecho -> Run As -> Launch on HW (GDB)

 

xil_printf ok
xil_printf ok


Si tenés un osciloscopio, conectá TX:

 

Serial en osciloscopio
Serial en osciloscopio

 

Conectando a computadora vía un UART y con una leve modificación, confirmamos que está funcionando ok y a 9600

 

Serial Ok
Serial Ok

 

Doble serial

 

Es lo mismo que lo de antes pero con dos seriales, no? No sé, primero tengo que ver si uartlite soporta cambiar baudrate... no. Queda hardcodeada en el, valga la redundancia, hardware. Por eso debe ser "lite". La segunda la voy a poner a 115200 por las dudas y luego investigaré si sirve o tengo que cambiar por algo más veloz.


Aunque sea tedioso, voy a rehacer todo desde cero, para practicar.

  • crear proyecto
  • agregar y ajustar xdc 
  • agregar zynq
  • block automation
  • agregar y configurar uarts
  • connection automation parcial
  • save block diagram
  • validate
  • agregar hdl wrapper
  • generar bitstream

Teniendo alguna uartlite, doble click hace el truco:

 

Seleccionar baudrate
Seleccionar baudrate

 

Cuando ya pusiste todo el hardware queda así:

 

Doble UART
Doble UART

El software debería ser igual, sólo que ahora tengo dos dispositivos.

 

Implemented Design
Implemented Design 

 

Hay un apenas perceptible aumento de uso de la FPGA, podríamos meter decenas de uartlites, quizás un centener si tuvieramos suficientes pines.
 

Sigamos la carrera...

  • export hardware
  • launch SDK
  • create project
  • import example
  • modify
  • burn
  • run as


ups, me pasé, tengo que volver a import example, me ofrece un ejemplo para cada uartlite, me imagino que cada uno tiene sus constantes que lo diferencian... no, son iguales.

Veamos la memoria dónde están:


Direcciones
Direcciones

Al comienzo de xuartlite_low_level_example.c hay una definición interesante, si te parás encima cuando es usada más abajo en main() te dice el valor:

 

Dirección UART 0
Dirección UART 0

 

La segunda UART dice ser 0x42c10000, perfecto, sólo es cuestion de manosear un poco el código:

 

#define UARTLITE_BASEADDR_0       XPAR_UARTLITE_0_BASEADDR
#define UARTLITE_BASEADDR_1       XPAR_UARTLITE_1_BASEADDR


    u8 data ='0';
    xil_printf("start\r\n");
    XUartLite_SendByte(UARTLITE_BASEADDR_0, data);
    data = '1';
    XUartLite_SendByte(UARTLITE_BASEADDR_1, data);


    while (1) {
        data =  XUartLite_RecvByte(UARTLITE_BASEADDR_0);
        XUartLite_SendByte(UARTLITE_BASEADDR_0, data);
        XUartLite_SendByte(UARTLITE_BASEADDR_1, data);
        xil_printf("Echoing and bridging %c\r\n", data);
    }


Para que se vea mejor en el osciloscopio, triple eco:


Código y uart_0 a 9600
Código y eco a uart_0 a 9600


Se emite cero, cero más uno, cero más dos


bridging triple a uart_1 a 115200
bridging triple a uart_1 a 115200



Proyecto funcionando
Proyecto funcionando



Anotados los componentes
Anotados los componentes

Amarillo: el adaptador usb-uart/ttl que se controla desde la terminal a 9600.

Rojo: los pines de conexión de uart_1 a 115200 conectados al osciloscopio.

Verde: los pines de conexión de uart_0 conectados a usb-uart/ttl.

Naranja: la terminal de la SDK, alimentación, programación, debugger...

Azul: tierra para el osciloscopio.


Conclusiones

Es muy sencillo agregar uarts, entre la herramienta y los ejemplos queda poco por hacer.


 

 



 


 





No hay comentarios:

Publicar un comentario