Mostrando entradas con la etiqueta pynq. Mostrar todas las entradas
Mostrando entradas con la etiqueta pynq. Mostrar todas las entradas

2021/12/21

Forzador MD5 en PYNQ, el software


Siguiendo los pasos detallados anteriormente, no bien creás el IP, te aparecen los drivers en:

./ip_repo/MD5_Brute_Forcer_1.0/drivers/MD5_Brute_Forcer_v1_0/src/MD5_Brute_Forcer.h

 

Los valores de desplazamiento de cada registro son:

#define MD5_BRUTE_FORCER_S_AXI_SLV_REG0_OFFSET 0
#define MD5_BRUTE_FORCER_S_AXI_SLV_REG1_OFFSET 4
#define MD5_BRUTE_FORCER_S_AXI_SLV_REG2_OFFSET 8
#define MD5_BRUTE_FORCER_S_AXI_SLV_REG3_OFFSET 12
#define MD5_BRUTE_FORCER_S_AXI_SLV_REG4_OFFSET 16
#define MD5_BRUTE_FORCER_S_AXI_SLV_REG5_OFFSET 20
#define MD5_BRUTE_FORCER_S_AXI_SLV_REG6_OFFSET 24

 y algunas funciones

#define MD5_BRUTE_FORCER_mWriteReg( \
  BaseAddress, RegOffset, Data) \
    Xil_Out32((BaseAddress) + \
  (RegOffset), (u32)(Data))

#define MD5_BRUTE_FORCER_mReadReg(BaseAddress, RegOffset) \
    Xil_In32((BaseAddress) + (RegOffset))



XStatus MD5_BRUTE_FORCER_Reg_SelfTest(void * baseaddr_p);

 

Tras generar el bitstream, exportar el hardware, abrís la SDK

 

Comprobás que exista lo tuyo, en mi caso MD5_Brute_Forcer:

 

En system.hdf vemos MD5_Brute_Forcer
En system.hdf vemos MD5_Brute_Forcer


Creás entonces una nueva "Application Project" con template de "empty application", que te va a crear una carpeta con el nombre que le pongas con sufijo _bsp. En mi caso, en:

./md5_brute_forcer/md5_brute_forcer.sdk/brute_forcer_bsp/ps7_cortexa9_0/include/

en xparameters.h  te define:


/* Definitions for peripheral MD5_BRUTE_FORCER_0 */
#define XPAR_MD5_BRUTE_FORCER_0_DEVICE_ID 0
#define XPAR_MD5_BRUTE_FORCER_0_S_AXI_BASEADDR 0x43C00000
#define XPAR_MD5_BRUTE_FORCER_0_S_AXI_HIGHADDR 0x43C0FFFF

tambien verás que MD5_Brute_Forcer.h ahora está en 

brute_forcer_bsp/ps7_cortexa9_0/include/MD5_Brute_Forcer.h

Creás un main.c

Un problema que no puede resolver es que los archivos xgpio.h  xgpio_l.h que proveen la resolución del nombre de las funciones más básicas de lectura y escritura en la memoria, no están siendo generados o incluidos. Lo que he hecho es tomarlos de uno de los ejercicios y copiarlos en 

brute_forcer_bsp/ps7_cortexa9_0/include/


Llegado este punto, hay que grabar el bitstream, "Xilinx" -> "Program FPGA" por un lado y ejecutar el programa por el otro con botón derecho sobre la aplicación, "Run as" -> "launch on hardware (GDB)" y usando por ejemplo 

miniterm.py  /dev/ttyUSB1  115200 


y a disfrutar de los resultados.

 

Un pipeline
Un pipeline


Dos pipelines
Dos pipelines

Cuatro pipelines
Cuatro pipelines


Como notarás:

En waited dice cuando charlyseconds demoró, va decreciendo.

En found plin plin plin at pipeline tal de tantas ves como evoluciona de 1 a 4 pipelines. El pipeline que lo halló es 1,2,4,8, si mirás el registro, entenderás.

Con los valores 200 a 203, como al valor hay que sumarle el pipeline donde fué hallado.

Y lo más notorio, hay un desplazamiento entre el valor esperado y el obtenido, es algo que siempre ha ocurrido, en las simulaciones tambien, me falta diagnosticar, por ahora se va a tener que ajustar desde el software, al igual que los primeros y últimos valores que no los puede hallar, motivo de otra entrada futura.

Y más notorio aún, para cuatro pipelines falla para un número relativamente bajo, tengo que revisar bien, pero me puede llevar mucho tiempo y como no hace a la implementación de software, por hoy no me preocupo.


Estos resultados no salen de un repollo, veamos el programa main.c, antes recordemos, recordemos, no, no recordemos, es de una entrada futura pero que estoy haciendo a la vez, en la del futuro digo que "de paso agrego más valores a los registros", pero bueno, aceptemos este efecto sobrenatural.


Registros
Registros


Lo primero es adaptar las primitiva provistas por el driver de bajo nivel a algo más usable.

Para habilitar y deshabilitar hay que escribir 1 y 0 en el registro 4, eso se obtiene de esta base y desplazamiento:


XPAR_MD5_BRUTE_FORCER_0_S_AXI_BASEADDR

MD5_BRUTE_FORCER_S_AXI_SLV_REG4_OFFSET

Las funciones son:

void enable() {
     MD5_BRUTE_FORCER_mWriteReg(
         XPAR_MD5_BRUTE_FORCER_0_S_AXI_BASEADDR,
         MD5_BRUTE_FORCER_S_AXI_SLV_REG4_OFFSET, 1
         );
}

void disable() {
    MD5_BRUTE_FORCER_mWriteReg(
      XPAR_MD5_BRUTE_FORCER_0_S_AXI_BASEADDR,
      MD5_BRUTE_FORCER_S_AXI_SLV_REG4_OFFSET, 0
    );
}


Para escribir el hash hay que escribir 4 registros de 32 bits en los registro 0 a 3:

void setHash(unsigned int r3,
             unsigned int r2,
             unsigned int r1,
             unsigned int r0) {
    MD5_BRUTE_FORCER_mWriteReg(
       XPAR_MD5_BRUTE_FORCER_0_S_AXI_BASEADDR,
       MD5_BRUTE_FORCER_S_AXI_SLV_REG3_OFFSET, r3
    );
    MD5_BRUTE_FORCER_mWriteReg(
       XPAR_MD5_BRUTE_FORCER_0_S_AXI_BASEADDR,
       MD5_BRUTE_FORCER_S_AXI_SLV_REG2_OFFSET, r2
    );
    MD5_BRUTE_FORCER_mWriteReg(
       XPAR_MD5_BRUTE_FORCER_0_S_AXI_BASEADDR,
       MD5_BRUTE_FORCER_S_AXI_SLV_REG1_OFFSET, r1
    );
    MD5_BRUTE_FORCER_mWriteReg(
       XPAR_MD5_BRUTE_FORCER_0_S_AXI_BASEADDR,
       MD5_BRUTE_FORCER_S_AXI_SLV_REG0_OFFSET, r0);
}



Para leer el resultado, está en el registro 5:


unsigned int readTarget() {
        return MD5_BRUTE_FORCER_mReadReg(
         XPAR_MD5_BRUTE_FORCER_0_S_AXI_BASEADDR,
         MD5_BRUTE_FORCER_S_AXI_SLV_REG5_OFFSET );
}


Para leer cuantos pipelines hay y cuál tuvo el hit, hay que leer el registro 6 y extraer los bits necesarios:


unsigned int readStatus() {
     return MD5_BRUTE_FORCER_mReadReg(
          XPAR_MD5_BRUTE_FORCER_0_S_AXI_BASEADDR,
          MD5_BRUTE_FORCER_S_AXI_SLV_REG6_OFFSET
     );
}


Lo mismo para la cantidad de pipelines y cuá halló el resultado:

unsigned int readPipelineCount() {
    unsigned int status = readStatus();
    return ( status & 0x0000f000 ) >>12;
}

unsigned int readPipelineHit() {
    unsigned int status = readStatus();
    return status >> 16;
}


Para mostrar los bits de estado:

void printStatus(const char * tab) {
    unsigned int status = readStatus();
    printf("%s",tab);
    if (status & 0x1)  printf("paused ");
    if (status & 0x2)  printf("running ");
    if (status & 0x4)  printf("warming ");
    if (status & 0x8)  printf("found ");
    if (status & 0x10) printf("done ");
    if (status & 0x20) printf("enabled ");
    if (status == 0) printf("errorNoStatus ");

    printf("\r\n");
}


Y eso es todo, otra vez anticipando el futuro, cuando pueda prescindir de los printf probablemente pueda usar un microblaze en la Nexys4DDR, quizás a la vez hacer un IP sin AXI pues no creo que entren los ocho pipelines y el microblaze a la vez.






2021/12/20

Forzador MD5 en PYNQ, el hardware

Este es el detalle del trabajo práctico de la materia Micro Arquitecturas y Soft Cores, ya comentada previamente.

El objetivo es instanciar el Forzador Brutal de MD5 en un chip ZYNQ de una placa PYNQ-Z2 proveniente de una Nexys4DDR que es más grande, ya habiendo fracasado durante la cursada de Basic Digital Design agregando VIO e ILA como interfaz. En esta oportunidad la interfaz será AXI hacia los cores donde se ejecutará un programita escrito en C.

 

Pasos

Necesito saber previamente algunos datos:

Número de registros para el periférico AXI

  • para determinar el hash (input, 128 bits, 16 bytes, 4 registers )
  • para obtener el valor (output, 32 bits, 4 bytes, 1 register)
  • control para iniciar proceso (input, 1 bit, 1 register)
  • status (output 3 bits, 1 register)
    • running
    • found
    • done

Son 7 registros.

 

Respecto a estos pasos, tengo un fuerte Déjà vu, es que ya lo he hecho antes pero entendiendo menos al hacer los ejercicios de dos libros.

 

Crear el IP

  • Tasks -> manage IP -> New IP Location
    • Part: xc7z020clg400-1
    • Target Language: Verilog
    • Crear y seleccionar una carpeta, en esta aparecerá todo lo que cree Vivado
  •  Tools -> Create and Package New IP
    • Create a new AXI4 peripheral
    • Name: ip_md5
    • Number of registers:7
    • Interfaces: quizás quitar el "00"
    • Next Step: Edit IP
  • Copiar los archivos del proyecto original a  ip_repo/ip_md5_1.0/hdl
    • Agregarlos con Add Design Sources
      • mejor NO copy sources into IP Directory
    • en este momento
      • ya se ha creado el C con los drivers de bajo nivel
      • es buena idea hacer un commit
  • Editar el ip de S_AXI, ip_md5_v1_0_S_AXI_inst
    • Acá va la magia, ver luego
  • Flow -> Run Synthesis
  • Flow -> Package IP 
    • identification
      • add category
    • file groups
      • merge changes from file groups wizard
    • customization parameters
      • merge changes from customization parameters wizard
    • ports and interfaces
      • hay que ver que esté lo que hayas puesto, en este caso status
    •  review and package
      • re-package IP
  • Otro buen momento para un commit 
  • close project 

 

 Crear el proyecto

  • File -> project -> new
    • location lo puse a la par que ip_repo
    • varios next
    • choose board -> pynq-z2
    • varios next
  • Settings 
    • Project Settings
      • IP
        • Repository -> ADD
          • lo del paso Crear IP
  • IP Integrator -> Create block design
  • Add IP -> zynq
  • run block automation
  • re-customize IP ( doble click)
    • ps-pl configuration 
      • general
        • enable clock resets
          • fclk_reset0_n
      • Axi non secure enablement
        • gp master axi interface
          • m axi gp0 interface ON
    • MIO Configuration
      • I/O Peripherals
        • UART 0 -> MIO 14::15
    • clock configuration
      • pl fabric clocks
        • fclk_clk0 -> 100
  • Add  IP ->
  • run connection automation
  • si tenías ports
    • make external
    • agregar el xdc y ajustarlo
  • validate design
  • source ->design_1.bd -> create hdl wrapper
  • source -> design_1.bd -> generate output products (paciencia)
  • Program and debug
    • generate bitstream (más paciencia)
  • file
    • export
      • export hardware -> include bitstream
  • file
    • launch sdk

SDK

  • comprobar en system.hdf que esté la IP y la uart
  • comprobar que en wrapper platform esten los drivers 
  • file
    • new
      • application project
        • empty application template
        • comprobar bsp-> ps7_cortexa9_0-> include -> ip_md5.h
  • el programa en C que quieras, otro día vemos más detalles.
  • botón derecho sobre el proyecto -> run as -> launch on hardware (GDB)
  • un segundo antes abriste la terminal:
    • miniterm.py  /dev/ttyUSB1  115200
    • y para un programa muy sencillo:

printf("MD5 Brute Forcer SelfTest\r\n");
   MD5_ACCELERATOR_Reg_SelfTest(
   XPAR_MD5_ACCELERATOR_0_S_AXI_BASEADDR
);

    • una salida muy sencilla:

Salida seft test
Salida seft test


Detalles

 

En el comienzo me equivoqué con el conteo de registros, pensé que eran 6, sin embargo fue intutitiva la corrección del hdl generado. Como por otros motivos tuve que arrancar de cero, al comparar la nueva generación con lo corregido comprobé que la corrección fué correcta.


Respecto a la paciencia en "Generate Output Products", nunca avisa que termina y arriba a la derecha el iconito de trabajando queda activo y dice "Synthesis out-of-date_". La manera de saber cuando ha terminado es mirando en la ventanita de Design Runs.


output products corriendo
output products corriendo

output products corriendo
output products finalizado


La magia


Primer nivel del wrapper


Si tu IP no usa puertos, no hay nada que tocar. En mi caso, para diagnosticar puse unos puertitos y se los conecté.

va en la interfaz:

      output wire [5:0] status,

en la instanciación de S_AXI:

      .status(status),

 

Segundo nivel del wrapper


Si tu IP no usa puertos, no hay nada que tocar en la interfaz, en este caso si, entonces:

        output wire [5:0] status,

Luego van los registros de salida:

    // Add extra user logic register here
    
    wire [C_S_AXI_DATA_WIDTH-1:0]    target_o;
    wire [C_S_AXI_DATA_WIDTH-1:0]    status_o;

    // I/O Connections assignments

En donde se leen, hay que reemplzar los registros slaves por estos nuevos:

// Address decoding for reading registers
case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
    3'h0   : reg_data_out <= slv_reg0;
    3'h1   : reg_data_out <= slv_reg1;
    3'h2   : reg_data_out <= slv_reg2;
    3'h3   : reg_data_out <= slv_reg3;
    3'h4   : reg_data_out <= slv_reg4;
    3'h5   : reg_data_out <= target_o;
    3'h6   : reg_data_out <= status_o;
    default : reg_data_out <= 0;
endcase


Y por último, la conexión de cada bit de status al registro de status y la instanciación de nuestro IP:

// Add user logic here
    assign status = {status_o[0],status_o[1],
    status_o[2],status_o[3],
    status_o[4],status_o[5]};
       
    driver driver (
       .CLK(S_AXI_ACLK),
       .CPU_RESETN(S_AXI_ARESETN),
       .target_selected({slv_reg3,slv_reg2,slv_reg1,slv_reg0}),
       .enable_switch(slv_reg4[0]),
       .target(target_o),
       .status_paused(status_o[0]),
       .status_running(status_o[1]),
       .status_warming(status_o[2]),
       .status_found(status_o[3]),
       .status_done(status_o[4]),
       .enabled(status_o[5])
    );
    // User logic ends

 

Cuando metés la pata en el IP


La recomendación que he recibido y que sufrí no respetar, es si algo está mal, empezar otra vez de cero. Me parece que no hace falta tanto como de cero, estoy seguro que cuando incorporé los puertos de diagnóstico no lo hice, pero si tirar la sdk.

 

Edición del IP


  • Tasks -> manage IP -> Open IP Location
  • elegir el ip -> edit in IP Packager
  • Corregir el HDL
    • Acá va la magia, corregida
  • Flow -> Run Synthesis
  • Flow -> Package IP 
    • quizás File Groups
    •  review and package
      • re-package IP

 

Editar el proyecto


  • File -> project -> recent
  • IP Integrator -> open block design
    • aparece un mensaje diciendo que hay que hacer upgrade
    • report ip status
      • upgrade selected
    • te ofrece generate output products (paciencia)
    •  
  • Program and debug
    • generate bitstream (más paciencia)
  • [opcional] borrar el .sdk para evitar conflictos
  • file
    • export
      • export hardware -> include bitstream
  • file
    • launch sdk


Respecto a borrar el sdk antes de reexportar, no te olvides de tener copia o todo versionado para recuperar lo que ya hayas hecho. Debido a que mi IP usa Xil_In  y Xil_out, provistos por xgpio.h, cada vez tuve que restaurarlos, obtenidos originalmente de lo generador por uno de los labs.

 

Simulación


Agregué un test bench conectado al componente y a ese testbench lo puse como top. Si ejecutas la simulación con el wrapper como top, estás muerto, tendrías que generar los estímulos para AXI, olvidate...



Notas relacionadas por el lado de EAMTA 2021:

 

2021/12/19

MAySC 2021 con el Forzador Brutal de MD5 en PYNQ-Z2 y Vivado 2018.2

He tenido el privilegio y placer de cursar Micro Arquitecturas y SoftCores a cargo de Nicolás Álvarez, excelente, no hay nada más que decir.

 

Había cursado la materia anterior también a su cargo, Circuitos Lógicos Programables, pero fué hace mucho y entre que olvidé algunas cosas como por ejemplo VHDL pues todos mis proyectos han sido en Verilog y que la materia incorporó algunos temas nuevos como VIO, ILA y uso de placas remotas, tuve unos ciertos apuros. Debido a que tengo una placa compatible con los laboratorios y no había exigencia en utilizar las placas remotas, había visto VIO e ILA en otro excelente curso Basic Digital Design con el Colo y Ariel Pola y ya había practicado algunos temas por mi parte de algunos libros y varios proyectos, no lo sufrí.

 

¿Para qué hacés un curso de algo que ya mayormente sabés?

 

Bueno, para empezar no sé tanto. Por un lado no trabajo de esto así que todo lo que aprendo lo voy olvidando paulatinamente por falta de uso, el "recursar" me lo refresca. Por otro lado, no es lo mismo lo que uno aprende de un libro, de un video, de experimentar en soledad que de una clase interactiva y más cuando quien enseña es del calibre de Nicolás.

 

El hardware


El curso se dicta con Vivado 2018.1 por que hay unas placas ArtyZ7-10 disponibles en el servidor que está con esa versión.


Yo ya tengo dos ZYNQs, una Parallella que es parecida a la Arty por el SoC (xc7z010clg400-1) pero no tiene nada de botones, switches y leds, sobre la cual intentaré publicar algo en un futuro cercano y una PYNQ-Z2, que vengo usando hace rato y su única diferencia a efectos prácticos del curso es que el SoC es xc7z020clg400-1, los ejercicios son menos que minúsculos como para que haga diferencia.

Respecto a Vivado 2018.1 vs Vivado 2018.2, no hallé diferencia...

 

El trabajo práctico

 

Los alumnos regulares tienen la posibilidad de hacer algo en el marco de su trábajo práctico de especialización. Eso me deja con hacer algo suficientemente difícil pero inútil o ver de reflotar alguna tarea del backlog y mirando mirando ¿qué mejor que tomar el queridísimo Forzador Brutal de MD5 y portarlo a la PYNQ aplicando las técnicas de encapsulamiento en IPs e interfaceando con los microprocesadores?

Durante la cursada de Basic Digital Design había fracasado en portar de Nexys4DDR a PYNQ-Z2 agregándole VIO e ILA. Lamentablemente no recuerdo bien cómo se manifestó ese fracaso, probé de usar un pipeline menos, la mitad. De hecho tomé esa versión con la mitad para este TP. Finalmente, como estaba trabado en un problema que no lograba resolver, reduje a dos pipelines para que Nicolás pudiera ayudarme. En el proceso de comprobar la correcta adaptación descubrí que no había hecho la adaptación correcta a cuatro pipelines y eso explica probablemente el fracaso anterior.

Otros errores que cometí fueron:

El reset lo invertí para dianosticar y me olvidé de revertirlo hasta que lo recordé.

Conecté un registro de entrada como uno de salida, mmh.

 

Un error no tan error 

 

No hice simulaciones desde el primer momento, pero de todos modos, el error más importante, el de la adaptación a menos pipelines, no saltaba en las simulaciones.

 

¿Qué me queda para hacer?

 

En las próximas notas explicaré como transité lo del hardware y el software del trabajo práctico. Luego intentaré parametrizar la cantidad de pipelines para poder usarlo indistintamente en la Nexy4DDR con 8, en la PYNQ con 4 y en la Parallella con 2, esto último tras haber tomado el control tanto tiempo postergado.


2020/11/11

PYNQ acelerador criptográfico con leak

Recapitulemos:


Primero había visto como agregar un puerto serial y usarlo desde la CPU, luego un sencillo acelerador criptográfico por hardware y finalmente, que los mensajes enviados por el puerto serial fueran cifrados por el acelerador.


Ha llegado el momento de hacer el ataque, que consiste en que el acelerador criptrográfico divulgue la clave o el mensaje no cifrado por algún canal alternativo.


Para ello, primero tengo que aprender a usar un IP en mi IP, esto es un componte existente en mi componente. Esto lo voy a hacer en un proyecto aparte, luego regresaré al proyecto que ya está funcionando y aplicaré lo aprendido.

 

IP en IP

 

Otra vez...


  • File -> New Project ->...
  • Create and Package New IP 
  • Create a new AXI4 peripheral
  • Edit IP
  • IP INTEGRATOR
  • Create Block Design

Mmm esto es un terrible problema, la uart no es simplemente una uart sino axi-uart, tendría que descifrar cómo usarla, quizás sea mucho más fácil y acorde al espíritu del ejercicio usar una uart verilog pelada.

Además sólo necesito un transmisor UART y teniendo el código fuente puedo prescindir con sencillez del receptor.


Fail

 

UART Transmitter copy paste


En la excelente página de nandland hay un ejemplo, veamos si puedo hacerlo funcionar, voy a editar el acelerador:


  • IP INTEGRATOR
  • Open Block Design
  • Window -> IP Catalog -> accelerator_xpr_v1.0 ->  Edit in IP Packager
  • Usar otro nombre, por ejemplo "accelerator_xor_bugger_v1_0_project"
  • Source -> + -> Add or Create constraints -> pynq-z2_v1.0.xdc

##Arduino Digital I/O 

set_property -dict {PACKAGE_PIN V17 IOSTANDARD LVCMOS33}
[get_ports { tx_1 }]; #IO_L21P_T3_DQS_34 Sch=ar[8]

  • Sources -> + -> Add or Create Design Sources -> Create -> UART_TX.v


Esta es la interfaz de módulo

module UART_tx
  #(parameter CLKS_PER_BIT = 870)
  (
   input       i_Clock,
   input       i_Tx_DV,
   input [7:0] i_Tx_Byte,
   output      o_Tx_Active,
   output reg  o_Tx_Serial,
   output      o_Tx_Done
   );

 

Mirando fijo el código, apuesto que hace falta un high en i_Tx_DV para que inicie la transmisión, llamémoslo "uart_send":


¿Qué va en cada port al instanciar?


    UART_tx UART_tx(
      .i_Clock(S_AXI_ACLK),
      .i_Tx_DV(uart_send),
      .i_Tx_Byte(slv_reg1[7:0]),
      .o_Tx_Active(uart_active),
      .o_Tx_Serial(tx_1),
      .o_Tx_Done(uart_done)
    );


S_AXI_ACLK: espero que sea de 100Mhz, sino hay que ajustar CLKS_PER_BIT.

uart_send: tengo que detectar que hay un caracter a enviar.

slv_reg1[7:0]: los caracteres vienen de a cuatro, por ahora sólo voy a transmitir el último.

uart_active: puedo ignorarlo y no conectarlo.

tx_1: va conectado al pin tx_1 declarado en xdc, es por donde fugará la información.

uart_done: puedo ignorarlo y no conectarlo.

 

Los que puedo ignorar se debe a que apuesto a que voy a transmitir más rápido que lo que me piden cifrar los caracteres y además como sólo voy a transmitir el menos significativo, con menor frecuencia. Para ser realistas, no puedo transmitir el texto plano a la velocidad que me lo puede llegar a pedir la CPU, tendría que transmitir sólo la clave. En realidad tendría que medir.

Dejando de lado esas medidas y optimizaciones, me conviene no ignorarlos, pues para implementar la lógica me vienen bárbaro.

Voy a tener que adaptar el programa para que tome cada caracter como un bloque de cifrado para no tener que implementar la lógica de ir transmitiendo de a cuatro caracteres, para lo cual sí necesitaría active y done.



Otra vez los mismo...

  • Source -> + -> Add or create constraints -> pynq-z2_v1.0.xdc
  • Tools -> Create and Package New IP
  • Create a new AXI4 peripheral
  • Name, version, etc...
  • Next Steps -> Edit IP
  • Finish
  • Source -> + -> Add or create design sources
    • uart_tx.v
    • ip_repo/leaky_accelerator_1.0/src/
  • Agregué un puerto tx_1 tanto a accelerator_bugged_v1_0 como a accelerator_bugged_v1_0_S00_AXI_inst y en el primero lo conecté al segundo
  • Agregué uart_tx y lo instancié
  • Agregué la lógica y los puertos extra de diagnóstico


  • Package IP - accelerator 
  • Review and Package
    • merge * changes
  • Re-Package IP

Volviendo al proyecto original

 Create Block Design

  • Add IP
  • Zynq
  • run block automation 
  • Add IP
    • accelerator_bugged
  • Add IP 
    • uartlite
  • run connection automation
    • axi 
  • axi_uart_lite
    • expandir UART
      • externalizar tx_0 y rx_0
  • accelerator_bugged
    • externalizar tx_1
  • Save Block Design
  • Tools -> Validate
  • Sources -> Design Sources -> leaky_accelerator -> botón derecho -> Create HDL Wrapper
  • Generate bitstream
  • File -> Export Hardware
    • include bitstream
  • File -> Launch SDK
  • File -> New -> Application Project
  • ajustar constantes
  • program device
  • run as...


Falla...

Cuando arranca tx_1_0 esta high como debe, al enviar el programa pasa a low


  • IP INTEGRATOR -> Open Block Design
  • IP Catalog -> leaky accelerator -> botón derecho -> Edit in IP Packager
  • Package IP
    • Review and Package -> merge changes
    • Re-Package IP
  • Detecta que hubo cambio de IP
  • Report IP Status
  • Re run report
  • Upgrade selected
  • Generate bitstream
  • File -> Export Hardware
    • include bitstream
  • File -> Launch SDK
  • System.mss -> Re-generate BSP Sources


y así muchas veces hasta que te dás cuenta del error, que es en uart_tx, ¡¡¡no es sólo mío!!! No te puedo ofrecer mostrarte a ver si vos te dás cuenta... probemos:


module uart_tx
  #(parameter CLKS_PER_BIT = 870)
  (
   input       i_Clock,
   input       i_Tx_DV,
   input [7:0] i_Tx_Byte,
   output      o_Tx_Active,
   output reg  o_Tx_Serial,
   output      o_Tx_Done
   );

...

  reg [2:0]    r_SM_Main     = 0;
  reg [7:0]    r_Clock_Count = 0;
  reg [2:0]    r_Bit_Index   = 0;
  reg [7:0]    r_Tx_Data     = 0;
  reg          r_Tx_Done     = 0;
  reg          r_Tx_Active   = 0;

....

// Wait CLKS_PER_BIT-1 clock cycles for start bit to finish
  if (r_Clock_Count < CLKS_PER_BIT-1)
    begin
      r_Clock_Count <= r_Clock_Count + 1;
      r_SM_Main     <= s_TX_START_BIT;
      end
    else

 

Medio que al seleccionar trozos de código te lo tiré en la cara, ¿no? No, ¿y ahora?


  #(parameter CLKS_PER_BIT = 870)
...
   input [7:0] i_Tx_Byte,
...
     if (r_Clock_Count < CLKS_PER_BIT-1)

 

 

¿Ya lo viste?


  #(parameter CLKS_PER_BIT = 870)
...
   input [7:0] i_Tx_Byte,
...
     if (r_Clock_Count < CLKS_PER_BIT-1)

 

Se necesitan más de 8 bits (256 elementos) para contener 870. Supongo que la persona que hizo el código original de nandland, que había puesto 87 pues contaba con un clock de 10Mhz no previó que la gilada iba a tener 100Mhz o más.


La solución es tan sencilla como


  reg [12:0]   r_Clock_Count = 0;


No tengo ahora tiempo para documentarlo bien, pero para el diagnóstico de este problema hice un proyecto que usa uart_tx y lo activa al apretar un botón:


module top(
  input sysclk,
  input btn_send,
  output led_trans_up,
  output led_state,
  output [2:0] ar
);

localparam char = 8'b10100011;
wire trans_up;
wire state;
wire [2:0] bus;

assign bus = ar;
assign led_state = state;
assign led_trans_up = trans_up;

uart_tx uart_tx(
   .i_Clock(sysclk),
   .i_Tx_DV(trans_up),
   .i_Tx_Byte(char),
   .o_Tx_Active(bus[0]),
   .o_Tx_Serial(bus[1]),
   .o_Tx_Done(bus[2])
);

debouncer debouncer_enable(.CLK (sysclk),
  .switch_input(btn_send),
  .trans_up (trans_up),
  .state(state)
);

endmodule



UART-Tx en acción en aislación
UART-Tx en acción en aislación



En realidad hice una simulación, cuando le bajé a 8 para no tener que esperar 870 ciclos para cada bit y ví que funcionaba, comprendí el error:



`timescale 1ns/1ps

module uart_testbench;
  reg simul_Clock;
  reg send;
  wire active;
  wire done;
  wire tx;

localparam char = 8'b10100011;

  initial begin
    simul_Clock = 1'b0;
    forever simul_Clock = #2.5 ~simul_Clock;
  end

  initial begin
    send = 1'b0;
    #100 send = 1'b1;
    #10 send = 1'b0;
   
  end

  initial begin
    repeat(64) @(negedge simul_Clock);
    $finish;
  end

uart_tx uart_tx(
   .i_Clock(simul_Clock),
   .i_Tx_DV(send),
   .i_Tx_Byte(char),
   .o_Tx_Active(active),
   .o_Tx_Serial(tx),
   .o_Tx_Done(done)
);

endmodule



Simulación de UART_Tx
Simulación de UART_Tx

Se vé bien clarito como el send pone en active, manda el start bit y al finalizar el stop bit y pasa a done.

 

Cuando comparta el código en github, va a estar en la carpeta uart_tx.


Finalmente, a la 1:am, a sólo 16 horas de la demo, por que todo esto es para H4CK3D 2020, tras lidiar y renegar con unas señales, logré que:

 

Entra "aaaa", sale "dddd"
Entra "aaaa", sale "dddd"



Tanto en un pin como en un led, la última letra del cleartext
Tanto en un pin como en un led, la última letra del cleartext


 

Para ahorrarme trabajo sólo estoy enviando la última letra de cada bloque, quizás en algún momento lo mejore. Tampoco se está mostrando las señales de done y send, igual no eran parte del objetivo, sólo diagnóstico, si el cyan que es active.

Sólo me faltaría el circuito para tomar esa señal desde el led en lugar del pin.


Este es el diseño final:


Diseño final
Diseño final



Quedan mucho ajustes por hacer, como el rango de la memoria ocupada, comprobar que los tiempos y consumos estén ok, comprender y mejorar la organización de los proyectos,  y además muchos componentes y recursos por usar, tengo la sensación de haber llegado a un 1% o menos de comprensión/conocimiento/experiencia con este tema, me siento como hace mucho tiempo cuando hice

10 print "hola"
20 goto 10
run

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


 



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.