2020/11/08

PYNQ con acelerador y serial

Si venís leyendo como generé el hardware y software para tener que la CPU del zynq-7020 de la PYNQ tuviera una terminal serial primero y luego un acelerador criptográfico de juguete luego y más si leiste la intro, no te sorprenderá que ahora quiera mezclar la cosas.

En este paso lo que haré será dejar el sistema, llamemoslo "legítimo" funcionando. Esto es, desde la PC me comunico por serial con la PYNQ que hace el cifrado y me lo devuelve. Vendría a ser un acelerador criptográfico remoto.

 

Para darle más sabor, en lugar de hacer todo a la vez, voy a agregar primero el puerto serial y luego el acelerador, pero en lugar de ir haciendo proyectos separados, los voy a ir modificando asi vemos como cambiar un proyecto. Es más, en la primera versión ya va hacer el cifrado, pero por software.


Creo el proyecto y lo pongo una uartlite, eso está suficientemente explicado o al menos relatado en las entradas anteriores pero me gusta escribir, la repetición forma parte del aprendizaje.



El serial


  • File -> Project -> New
  • IP INTEGRATOR -> Create Block Design
  • Add IP -> ZYNQ -> Run Block Automation
  • Add IP -> Uartlite -> Run Connection Automation -> S_AXI
  • axi_uartlite_0 -> UART -> expand
  • rx -> botón derecho -> make external (tomar nota del nombre del pin)
  • tx -> botón derecho -> make external (tomar nota del nombre del pin)
  • Sources -> + -> Add or create constraints -> pynq-z2_v1.0.xdc
  • Usar los nombres anotados en el xdc

 
##Arduino Digital I/O 
 
set_property -dict { PACKAGE_PIN T14   IOSTANDARD LVCMOS33 } [get_ports { tx_0 }]; #IO_L5P_T0_34 Sch=ar[0]
set_property -dict { PACKAGE_PIN U12   IOSTANDARD LVCMOS33 } [get_ports { rx_0 }]; #IO_L2N_T0_34 Sch=ar[1]


  • File -> Save Block Design
  • Tools -> Validate
  • Sources -> Design Sources -> remote accelerator -> botón derecho -> Create HDL Wrapper
  • PROGRAM AND DEBUG -> Generate Bitstream
  • File -> Export -> Export Hardware -> include bitstream
  • File -> Launch SDK
  • File -> New -> Application Project
  • Templates -> Empty Application
  • Project Explorer -> remote_accelerator -> src -> new -> file -> main.c

A main.c le pegamos el código de lo hecho en una terminal serial y le agregamos la aceleración por software, o sea, data ^= 3;

 

Aceleración por software
Aceleración por software

 


Si te preguntás de dónde sale XPAR_UARTLITE_0_BASEADDR, es de  

/remote_accelerator_bsp/ps7_cortexa9_0/include/xparameters.h

y debe coincidir con axi_uartlite_0 de system.hdf


Volvés un momento a Vivado

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

 

Regresás a SDK 

  • Project Explorer -> botón derecho -> Run As -> Launch on Hardware (GDB)

 

Abrís una terminal a un adaptador USB-UART/TTL que esté correctamente conectado a los puertos Arduino de la PYNQ, tipeas "abcd " y te va respondiendo:

 

Aceleración por software
Aceleración por software


Lista una parte, es un buen momento para cerrar todo y versionar.

 

El acelerador

 

Ahora tendría que abrir el diseño del bloque (Open Block Design) para agregar el acelerador, pero es parte de otro proyecto, no se vé desde acá, antes hay que descubrir como traspasarlo.


Settings -> IP -> Repository -> buscar la carpeta "IP Repo"

 

Repositorio de usuario agregado
Repositorio de usuario agregado

Luego arrastrás accelerator_xor al Diagrama, "Run Connection Automation", es casi vergonzoso lo fácil que resulta.



Diagrama completo
Diagrama completo

  • File -> Save Block Design
  • Tools -> Validate Design
  • PROGRAM AND DEBUG -> Generate Bitstream
  • File -> Export Hardware -> include bitstream
  • File -> Launch SDK


Si miramos el system.hdf, veremos ahora dos direcciones para nuestros dispositivos:

 

Los dos dispositivos
Los dos dispositivos


El código final
El código final


La interacción con el acelerador:

 

Interacción con acelerador
Interacción con acelerador



 a  b  c  d     caracter entrada
61 62 63 64     código ascii entrada

05 05 05 05     exor

64 67 66 61
     código ascii salida
 d  g  f  a
     caracter salida


 0  1  2  3
     caracter entrada
30 31 32 33
     código ascii entrada

05 05 05 05     exor

35 34 37 36
     código ascii salida
 5  4  7  6    
caracter salida

 

 Y las cuentas comprobadas:

 

La cuentas
La cuentas

 

 

Notarás que aunque de alguna manera está haciendo cifrado por bloques, como puse 0x05050505 como key es como si hiciera cifrado por caracteres.

PYNQ con acelerador

En pocas palabras para no repetir lo dicho antes:

 

Necesito hacer un acelerador criptográfico en la PL (Programmable Logic) de un Zynq-7020 (PYNQ en particular) accesible desde un programa que se ejecute en el PS (Processing System) o como todos le conocemos, CPU.

Ya había logrado usar IP (la forma en que llaman a los componentes en este mundo), en particular dos UARTs. Ahora quiero hacer mi propia IP y que haga un cirfrado sencillo.

No voy a apoyarme en una experiencia anterior con Parallella sino en los ejercicios del libro comentado que he estudiado.

 

Estos proyectos están a la par de los dos proyectos "serial" y "dual-serial"  y también lo construiré de cero sin ninguna dependencia cruzada para evitar complicaciones, apuntando a las siguientes hitos:


  • Acelerador echo: que me devuelva lo que escribo.
  • Acelerador xor: que calcule el xor de dos argumentos en un tercero.

 

 

Acelerador con eco 


Manos a la obra, esta parte es común a todos los proyectos anteriores:

  • 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

 

Acá empieza la diferencia, crear el IP

  • Tools -> Create and Package New IP
  • Create a new AXI4 peripheral
  • Name, version, etc...
  • Next Steps -> Edit IP
  • Finish

 

Ahora se pone delicado, hay que buscar el archivo que te creó y al final hay un comentario que dice "Add user logic here", nada por ahora.


Edición del acelerador
Edición del acelerador




Más arriba, buscando "slv_reg0" encontras los nombres de los registros para comunicarte con la CPU. Son todos de 32 bits.

Voy a usar

  • slv_reg0 como clave
  • slv_reg1 como input
  • slv_reg2 como output 
  • slv_reg3 como contador, ya que está...

 


Lo primero que tengo que hacer es que me funcione como memoria, o sea, escribo algo y luego lo vuelvo a leer. Si está ok, estamos encaminados, sigamos entonces.

Hay que ir al tab:

  • Package IP - accelerator
 Abajo de todo va a estar

  • Re-Package IP

 

Listo, cerró, volvemos al proyecto original.

  • Create Block Design
  • Add IP
  • Zynq
  • run block automation 
  • Add IP
  • Buscar el nombre, en este caso accelerator
  • run connection automation
  • Save Block Design
  • Tools -> Validate
  • Sources -> Design Sources -> tu proyecto -> botón derecho -> Create HDL Wrapper
  • Generate bitstream
  • File -> Export Hardware
    • include bitstream
  • File -> Launch SDK

 

Lo que queda es puro software

  • File -> New -> Application Project
  • Templates -> Empty

 

Para encontrar la dirección del acelerador, hay que mirar system.hdf:


Dirección del acelerador
Dirección del acelerador

 

Con este sencillo programa pruebo que funciona, lo que escribo lo puedo volver a leer:

 

#include <stdio.h>
#include "xil_printf.h"
#include "xil_io.h"

#define BaseAddress    0x43c00000
#define REG_KEY        0
#define REG_CLEARTEXT  4
#define REG_CIPHERTEXT 8


int main() {
  u32 key;
  u32 clearText;
  u32 cipherText;

  xil_printf("Accelerator\n\r");
  for (u32 i=0; i< 255; ++i) {
    key        = Xil_In32(BaseAddress + REG_KEY);
    clearText  = Xil_In32(BaseAddress + REG_CLEARTEXT);
    cipherText = Xil_In32(BaseAddress + REG_CIPHERTEXT);

    xil_printf("i          : %d\n", i );
    xil_printf("key        : %d\n", key );
    xil_printf("clear text : %d\n", clearText );
    xil_printf("cipher text: %d\n", cipherText );

    Xil_Out32(BaseAddress + REG_KEY, i);
    Xil_Out32(BaseAddress + REG_CLEARTEXT, i);
    Xil_Out32(BaseAddress + REG_CIPHERTEXT, i);
    sleep(5);
  }
  return 0;
}


La ejecución en la terminal:



echo Ok
echo Ok
 

Acelerador XOR

 

Recorrí un tortuoso camino, con muchas pruebas fallidas. Cuando finalmente me rendí y busqué en internet, lo primero que hallé fueron estos minutos de 4 a 8, que tampoco funcionó, hasta que me dí cuenta de mi error:

#define BaseAddress    0x43c0000


¿Lo viste? Me quería matar, bueno, no importa, pensé bastante, el acelerador funciona y recorrí el camino de editar IP y reintegrarlo hasta el aburrimiento. Prometo que voy a hacer un machete de cómo hacer esto, pero despues, en diciembre que ahora estoy hasta las manos.

 

Nota del futuro: el paso anterior de echo también tenía ese problema, pero no se manifestó pues la dirección errónea no coincidió con nada que se pudiera romper. Lo corregí y dió ok.


El código Verilog, en verde mis agregados y modificaciones:


Código Verilog xor
Código Verilog xor

Los dos bloques calculan tanto el xor como el incremento del contador, cuyos registros están conectados a los Out que permanentemente están conectados a los registros 2 y 3. No me gusta mucho pues claramente el contador está contando el clock, no la cantidad de pedidos de cifrado y el exor se hace en cada ciclo de clock, es demasiado veloz, qué pasa si quiero hacer un cifrado de verdad que no llegue a hacerse en tal breve intervalo o si quiero hacer el ataque pensado? Bueno, eso esa el próxima entrega o la siguiente...

 

El programa tal como está ahora:


#include <stdio.h>
#include "xil_printf.h"
#include "xil_io.h"

#define BaseAddress    0x43c00000
#define REG_KEY        0
#define REG_CLEARTEXT  4
#define REG_CIPHERTEXT 8
#define REG_COUNT      12

int main() {
  u32 key;
  u32 clearText;
  u32 cipherText;
  u32 count;

  xil_printf("Accelerator\n\r");

  Xil_Out32(BaseAddress + REG_KEY, 3);

  for (u32 i=0; i< 255; ++i) {
        Xil_Out32(BaseAddress + REG_CLEARTEXT, i);

    key        = Xil_In32(BaseAddress + REG_KEY);
    clearText  = Xil_In32(BaseAddress + REG_CLEARTEXT);
    cipherText = Xil_In32(BaseAddress + REG_CIPHERTEXT);
    count      = Xil_In32(BaseAddress + REG_COUNT);

    xil_printf("i          : 0x%08x\n", i );
    xil_printf("key        : 0x%08x\n", key );
    xil_printf("clear text : 0x%08x\n", clearText );
    xil_printf("cipher text: 0x%08x\n", cipherText );
    xil_printf("count      : 0x%08x\n", count );

    sleep(5);
  }
  return 0;
}

 

Y la salida, comparando con lo esperado en la calculadora:


Ejecución Ok
Ejecución Ok