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

2022/09/28

Nerdearla 2022 Open de todo, la previa

El taller en realidad no es tan taller, ¿cuántas personas del ámbito de nerdearla tienen una edu-ciaa-fpga, el entorno armado e interés en hacer un taller de esto? Mmmh, poquita. En realidad es una charla larga, es que hay mucho tema pero aprovecho para que quien tenga la edu-ciaa-fpga y ganas, pueda ir probando en el momento, tipo taller.

Tal como he contado mi experiencia en https://seguridad-agile.blogspot.com/2021/09/h4ck3d-2021-configuracion-basica.html, el proceso para poder generar el bitstream es larguísimo. Para facilitar la vida de quien lo haga, he tomado la virtual que uso habitualmente y le apliqué un proceso de limpieza, sanitización y compresión para ahorrar ancho de banda por un lado y para ahorrar espacio en el disco de quien la instale por el otro.


En mis últimos proyectos similares he utilizado para las virtuales Ubuntu Server con xorg y openbox, pero puede ser demasiado minimalista para otras personas, así que en lugar de rehacerlo con Ubuntu Server, reduciendo aún más el tamaño de la imagen, me concentré en quitar. El proceso de limpieza fue eliminar toda aplicación superflua, caches, logs, los repositorios utilizados para la construcción de las toolchains, lo que sea, esto quizás lo detalle en otro post uno de estos días.

Las precauciones que he tomado al exportar es cambiar la contraseñas y fundamentalmente eliminar de .ssh la key que uso para github. Tras el proceso de zerofill y compactación, no debería quedar ningún rastro forense, pero como no tengo garantías, de todos modos también eliminé la key en github.

Reformulando, eliminé la key en github para no tener ningún problema de robo de credenciales y de modo secundario la purgué en la imagen para que nadie que no ha leído esto la encuentre y piense que fuí descuidado. Y fundamentalmente para predicar con el ejemplo.

El repo consideré borrarlo también y que hicieras git clone, pero de un modo u otro tenías que bajarlo, así que lo dejé.


Durante la charla/taller lo que iré mostrando es el proceso asumiendo que ya tenés todo instalado y funcionando, comprenderás que no voy a poder en ese momento resolver ningún problema. Para poder participar del taller probando los ejemplos, es necesario que ejecutes la siguiente secuencia de operaciones, que terminaría en la grabación del bitstream a la placa y como control deberías tener algunos leds cambiantes en la placa y en la terminal serial un menú.

 

Pasos

 

Asumo que si estás jugando con FPGA y más edu-ciaa-fpga, ya tenés bastante experiencia con linux y virtualización. Aún así, cualquier problema que tengas agregalo a los comentarios abajo e iré completando el instructivo.

 

El anfitrión

 

Tenés que agregar tu usuarios a estos grupos:

$ sudo addgroup "$USER" vboxusers

$ sudo addgroup "$USER" dialout

 

Instalá el Extension Pack si no lo tenés.


La imagen

 

La imagen exportada a OVA ocupa unos 3GB y está en un drive.

La virtual importada ocupa al menos 9.3 GB, con el uso no debería crecer mucho, pero considerá que la partición es de 30 GB. Es un Linux Mint 19 o 20, lo cual provée un entorno gráfico agradable.

Desde VirtualBox elegí "File", "Import Appliance"

Yo la uso con 8GB de RAM, pero seguro que anda con menos, mucho menos, tipo 3GB.

Configurá networking en modo bridge 

 

Arranque


Luego la arrancás, el usuario es "Charly", la clave "educiaa", abrís una terminal y averiguás la IP:

$ ip a

El repo está en:

$ cd ~/Desktop/repo/github/cpantel/evilcodesequence

 

El bitstream

 

$ make PROGRAM=fulldemo system 

 

El flasheo

 

Para evitar tener que conectar la placa a la virtual, que es bastante sencillo pero puede ser fuente de inconvenientes, el flasheo se hará desde el anfitrión si es linux, si es windows, vas a tener que hacerle funcionar el USB y flashear desde la VM, vemos luego.

En el anfitrion, ejecutar una sola vez:


$ scp charly@192.168.1.xx:/usr/local/bin/iceprog .


Luego, para cada nueva regeneración del bistream:


$ scp charly@192.168.1.xx:/home/charly/Desktop/REPO/github/cpantel/evilCodeSequence/BUILD/top.bin .

$ ./iceprog top.bin

 

si dice 

 

./iceprog: error while loading shared libraries: libftdi.so.1: cannot open shared object file: No such file or directory

 

Es por que te falta

 

$ sudo apt install libftdi1


Si optaras por conectar directamente la placa a la VM


$ make PROGRAM=fulldemo flash


Considerá que flashear desde el anfitrión que es MUCHO MUCHO más rápido.

Luego, usando tu programa favorito a /dev/ttyUSB1 con velocidad 9600, tendrías que ver algo como esto:

 

Menu
Menu

Jugá con las opciones, mejor la (1) LEDS pues el resto está a medio implementar en la imagen.

Contactame ANTES del taller si algo no te funciona, durante el taller no va a haber tiempo.

Hay otras maneras de obtener un entorno equivalente, ya sea que lo hagas vos de cero o que uses docker, es irrelevante siempre y cuando puedas compilar el programa, construir el bitstream y flashearlo, proveo la virtual como una facilidad.



2022/05/04

Parte de Flisol 2022

En esta oportunidad expuse un mezcladito de varios open:

Open HW/Core/SW: SoC ICICLE con CPU RISC-V en EDU-CIAA-FPGA

A mi propuesta original que incluía el ataque mostrado en H4CK3D 2021, el Profesor Matías me recomendó podarla para que sea menos técnica. Esa poda fué insuficiente, ya el título es bastante largo y complicado, a la mayor parte de la asistencia no era un tema que le interesara, no importa, para FLISoL 2023 tengo pensado algo más apropiado.

 

Mi idea fue ir evolucionando distintas implementaciones de la luz ondulante de K.I.T.T. hasta llegar a un softcore en FPGA, aprovechando el trabajo de un montón de gente:

 

Historia
Historia

 Lo rojito a la derecha era mi objetivo, lo expuesto en H4CK3D 2021

 

La primera versión es con componentes digitales, la hice hace casi 35 años, un registro de desplazamiento con unas puertas OR animadas a continuación, con un 555 para el clock, unos OR, un flip flop y algún capacitor para  inyectar el primer bit.

Ese bit entra en la primera posición del registro de desplazamiento y en cada tick del clock se va moviendo, con la salida conectada a la entrada con un OR al flip flop que salvo en el arranque siempre está en cero, lo tenemos para siempre. Yendo hacia los leds, con los OR de la derecha transformamos el movimiento en un aparente ida y vuelta.

K.I.T.T. con compuertas digitales
K.I.T.T. con compuertas digitales

 

Comparando precios, el costo es similar a implementarlo con un microcontrolador. Ponemos un bit en uno y lo vamos desplazando para un lado hasta detectar que llegó al punto deseado, ahí invertimos el sentido del movimiento, para siempre.


K.I.T.T. con microcontrolador y lógica específica
K.I.T.T. con microcontrolador y lógica específica

El programa es corto pero complicado e inadaptable a otros patrones. Una versión mejor aunque te indigne desde el punto de vista de la programación es:

while (true) {
  gpio_A.out(1);
  delay(DELAY);
  gpio_A.out(2);
  delay(DELAY);
  gpio_A.out(4);
  delay(DELAY);
  gpio_A.out(8);
  delay(DELAY);
  gpio_A.out(16);
  delay(DELAY);
  gpio_A.out(8);
  delay(DELAY);
  gpio_A.out(4);
  delay(DELAY);
  gpio_A.out(2);
  delay(DELAY);
  gpio_A.out(1);
  delay(DELAY);
}

¿Por qué me atrevo a incluir esta manera? Pues por que es el precursor para esta version más linda, en lugar de código hardcodeado, la información está en un array:

unsigned int out[]={1,2,4,8,16,8,4,2,1};
int pos = 0;
int limit = sizeof(out)/sizeof(out[0]);

while ( true) {
  gpio_A.out(1);
  delay(DELAY);
  ++pos;
  if (pos == limit) {
    pos = 0;
  }
}

y esto se parece mucho a la implementación nuevamente con componentes digitales.

K.I.T.T. con contador y memoria
K.I.T.T. con contador y memoria
 

Ahora, tanto en hardware como en software, podemos con gran facilidad mostrar otros patrones manipulando la memoria o el array según corresponda:

 

K.I.T.T. con contador y memoria y patrones arbitrarios
K.I.T.T. con contador y memoria y patrones arbitrarios

 

En la charla acompañando a esta evolución también fui contando como funciona una computadora a un nivel más bajo, que me cuesta mucho convertir a este formato, quizás haga un video en algún momento, lamentablemente no pude grabar la sesión.


Eso llevó a la explicación de FPGA y RISC-V, desembocando en el tema que me interesaba, la implementación primero en Verilog de la versión con lógica, como dispositivo incluido en el SoC icicle:

https://github.com/cpantel/evilCodeSequence/blob/master/kitt.sv

module kitt #( parameter BASETIME) (
    input clk,
    input reset,
    output [4:0]display_out,
    /* memory bus */
    input [31:0] address_in,
    input sel_in,
    //input read_in,
    //output logic [31:0] read_value_out,
    input [3:0] write_mask_in,
    input [31:0] write_value_in,
    output logic ready_out
);

    logic [25:0]q;
    logic direction;
    logic [4:0]display;

    assign ready_out = sel_in;
    assign display_out = display;

    always_ff @(posedge clk) begin
        if (reset) begin
            display <= 1;
            direction <= 1;
        end else if ( q > ( BASETIME / 1000 * 300 ) ) begin
            q <= 0;
            if (direction ) begin
                 if ( display[4] ) begin
                     direction = ~ direction;
                     display <= display >> 1;
                 end else
                     display <= display << 1;
            end else begin
                 if ( display[0] ) begin
                     direction = ~ direction;
                     display <= display << 1;
                 end else
                     display <= display >> 1;
            end 
       end else begin
            q <= q + 1;
            display <= display;
       end
    end

endmodule

 

Y por software, de modo concurrente y absolutamente independiente, ejecutándose en la CPU:

 

/*
  This program implements a SW kitt
*/

#include <stdint.h>
#include "../memmap.h"
#include "../uart.h"
#include "../delay.h"


int main() {
    uart_init();

    uart_puts("KITT starting\r\n");
    for (;;) {
        LEDS = 1;
        delay();
        LEDS = 2;
        delay();
        LEDS = 4;
        delay();
        LEDS = 8;
        delay();
        LEDS = 4;
        delay();
        LEDS = 2;
        delay();
        uart_puts("KITT .\r\n");
    }
}

 

 

Si algo entendés de Verilog, te darás cuenta que ese dispositivo como que está en vano conectado a los buses, ya que no hay nada con lo que el programa en la CPU pueda interactuar. Es que me quedé sin tiempo, querría al menos haberle permitido cambiar la velocidad.

En una próxima entrega espero no muy lejana, mostraré la implementación de un nuevo dispositivo que he de llamar "sequencer", que consistirá en una lógica que lea un trozo de memoria y lo exponga en los leds (vía PMOD como es ahora kitt) y que desde el programa se pueda arrancar, detener, pausar, cambiar la velocidad y modificar esa memoria.


 

 



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/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.


2021/11/15

H4CK3D 2021: el ataque concreto

Ya casi estamos, tenemos...


Los componentes del ataque 


Voy a asumir que sabés qué es un pipeline, que entendés un poco de Verilog y lo más difícil, que leiste todo lo anterior o al menos le pasaste una mirada por encima.


Propagación de la instrucción por todo el pipeline  


rv32 implementa un pipeline clásico de 5 stages según el autor y cita de una a la wikipedia.

En uno de esos stages hay que detectar la ejecución de las instrucciones interesantes y en otro hacer la modificación. Para ello tuve que, aprovechando la infraestructura de verificación formal, propagar la instrucción a todo el pipeline. Esto es por que en fetch y decode existe la instrucción, pero luego desaparece, no hace falta.

 

Propagación de la instrucción a todo el pipeline
Propagación de la instrucción a todo el pipeline


Las señales prefijadas con "`" son las agregadas.

 

Extracción de señal del RTC

 

A la interfaz agregué:


output attack_rtc_enable,


y luego:


assign attack_rtc_enable = secondsHi[1];

Esto significa que cuando el bit de valor 2 del dígito de las decenas de segundo esté  prendido, o sea 2 a 3 y 5 (segundos 20 a 39, 50 a 59), el ataque estará habilitado desde el punto de vista del tiempo.

Esta restricción temporal existe para disminuir las probabilidades de que se active durante una etapa de testeo. En un escenario realista, sería un combinacional tipo


assign attack_rtc_enable = hours[3] && minutesLo[3] && secondsHi[1];

 

Para que el ataque esté activo cuando lleve encendido 8 a 15 horas, en el minuto 04, 14, 24, 34, 44, 54 (creo) y los segundos con los que veníamos.

 

FSM en el stage de writeback detector de la secuencia de interés

 

Nada especial, una FSM de 8 estados, pude haber usado menos quizás, tomando estos valores:

localparam
  instr_0 = 32'hfef400a3, // sb  a5,-31(s0)
  instr_1 = 32'hfe244703, // lbu a4,-30(s0)
  instr_2 = 32'h00800793, // li  a5,8
  instr_3 = 32'h02f71a63, // bne a4,a5,30c <main+0x218>
  instr_4 = 32'hfe144703, // lbu a4,-31(s0)
  instr_5 = 32'h00100793, // li  a5,1
  instr_6 = 32'h02f71063; // bne a4,a5,304 <main+0x210>

 
En cada estado se pregunta si está el valor siguiente. Si está se pasa al siguiente estado, sino, regresa al comienzo.  Llegada la instrucción cuarta, se activa el ataque.
 
 

waiting_instr_3: begin
  if (instr_in == instr_3) begin  // bne a4,a5,30c <main+0x218>
    attack_state      <= waiting_instr_4;
    attack_seq_enable <= 1;
  end else begin
    attack_state      <= waiting_instr_0;
    attack_seq_enable <= 0;
  end
end
 
Por las dudas dejé el ataque activo por dos ciclos más.

 

Modificador de registros en regs según condiciones

 

Pasé de:

if (!writeback_flush_in && rd_write_in && |rd_in)
  regs[rd_in] <= rd_value_in;

que dice que si no se está flusheando el pipeline que ponga en el registro apuntado por rd_in el valor en rd_value_in.

Pasé a, decía:

if (!writeback_flush_in && rd_write_in && |rd_in)
  if (attack_seq_enable && attack_rtc_enable) begin
    regs[4] <= rd_value_in;
    regs[5] <= rd_value_in;

  end else begin
    regs[rd_in] <= rd_value_in;
  end

Que dice que si se detectó la secuencia y es la hora apropiada, ponga en los registros 4 y 5 el valor en rd_value_in.

No pude hacer que funcione asignando sólamente a5 <= 1, me parece recordar que tenía que poner una lógica toda complicada, así resultó ser lo más sencillo.

 

Conexionado

 

No merece una sección aparte, pero obviamente tuve que agregar todo el cableado pasando por las interfaces. Esto es por que no encontré rápido, es más, me parece que al menos con las tools que usé no es factible saltearse las interfaces y conectar directamente los componentes. De esto tengo ideas, pero eso lo veremos en "the missing pieces".

 

 


H4CK3D 2021: el escenario y el programa víctima

Por si no querés leer todo lo anterior, te resumo, es un ataque desde el hardware al software inspirado en un hecho real, una falla de software en un sistema crítico.

Dependiendo del flujo del programa, detectar que está ejecutando un “momento interesante” y producir una alteración en el comportamiento.

 

El escenario real sería un expendedor de cervezas, un bloque recibe el pedido y otro lo autoriza. El funcionamiento normal es apretamos el botón de "dame cerveza" y si el botón de "ok" está apretado, te la da. En la demo es con una barrera y los botones son "abrir" y "autorizar".

Tras el ataque, si vamos en un cierto momento y apretamos el "dame cerveza", sale independientemente de que "ok" esté apretado.

 

El programa víctima es entonces:

 

while (1) {

sensor1 = gpio1.read();     // leer botón 1

sensor2 = gpio2.read();     // leer botón 2

if (sensor1) {              // si pide cerveza...

if (sensor2) {          // si está autorizado...

gpio3.write(1);     // abrir

} else {

printf(“no way\n”); // denegar

}

}

}

El diseño atacante:

if ( secuencia de instrucciones && condición de tiempo) {

register = 1;

}


Recordemos que para hacer posible e interesante este ataque, agregué un módulo de RTC y otro de servo, un PWM restringido, superpuesto con la placa:

 

SOC icicle en edu-ciaa-fpga
SOC icicle en edu-ciaa-fpga


Anticipándome, el ataque estará en bien adentro de rv32 y con una pista desde el RTC.

 

Explicación del programa

 

Para comprender el siguiente código necesitás saber unas pocas instrucciones.

 

La lectura de los botones

 

Lectura de botones
Lectura de botones

 

lui: Carga en a5 el valor 0x10 desplazado doce bits a la izquierda

addi: Le suma 4 a a5, observá que coincide con 0x10004 como corresponde con el código HDL y el código C.

lw: Lee en a5 la memoria apuntada por a5, o sea, 0x00010004, o sea, el valor de los botones.

zext.b: esto es el equivalente a andi rd,rs,255, le mete todo cero a los 24 bits superiores de a5.

not: invierte los valores, corresponde a ~.

sb: guarda en lo apuntado por s0 menos 29 lo que haya en a5


fast forward, estamos en...

 

La zona de interés

 

Zona de interés
Zona de interés

El punto de ataque es el naranja, queremos que independiente de los botones, no tome el branch, que pase directo a "state = BarrierUp;"

Para ello, necesitamos que a4 y a5 sean iguales. En el punto rojo, cuando se cargue ese 1 en a5, necesitamos forzar esa igualdad.

Antes, la zona amarilla es la secuencia que nos señala que el ataque debe activarse.

Despues, la instrucción resaltada en verde pisa el valor de a5 antes de que sea usado, esto produce que no hayan efectos colaterales relacionados a a5, lo mismo pasa con a4, pero búscalo vos en el código.


Hay varias maneras de obtener este archivo, como es pedirle a gcc que compile a assembly, que conserve las piezas intermedias o lo que hice, decompilar:

riscv64-unknown-elf-objdump main.o -d -S > main.s

-d es disassemble

-S es que incluya el código fuente

 

Pero no es tan fácil, ese main.o no está aún ubicado en el lugar definitivo del programa ni tiene resueltas las direcciones de salto, es entonces conveniente:


riscv64-unknown-elf-objdump progmen -d -S > progmem.s


y ahí te van a saltar unas diferencias, nos quedamos con los valores de la derecha.


main.s vs progmem.s
main.s vs progmem.s

En amarillo la diferencia por desplazamiento, en naranja los placeholders de los saltos y su resolución y en rojo el código binario correspondiente.


Esta bueno comprobar que el código de máquina corresponda:

 

Zona de interés
Zona de interés

Pero, siempre hay un pero, RISC-V es little endian, así que hay que darlos vuelta en el .hex:

Zona de interés en el binario final
Zona de interés en el binario final

Fijate que hay una colisión con uno de los pasos de la secuencia, de todos modos no es el primero, no hay problema.


Hasta acá ya tenemos que conocemos la secuencia previa y sabemos cuál instrucción debe ser afectada.


Si encontramos una parte de esa secuencia, hay que cambiar unos registros. ¿Cómo? ya veremos...






 



 



2021/10/11

H4CK3D 2021: mejor resolución de dependencias en Makefile

Te recuerdo que todo esto tiene el objetivo de armar un ataque al comportamiento de un programa desde el hardware para mostrar en H4CK3D 2021. Como productos colaterales, una mejor adaptación de grahamedgecombe/icicle a ciaa/icicle, en parte aceptada como PR, en parte quedará en cpantel/evilCodeSequence y un montón de aprendizaje de mi parte, que te comparto como siempre, más si venís de leer lo anterior.

El problema que vengo arrastrando es el tiempo:
  • yosys: 1:10
  • nextpnr-ice40: 1:45
  • iceprog: 2:00

 

No puedo evitarlos, pero como habías visto en la entrada anterior, hay dos ramas, una para el hardware y otra para el software. Convergen en la que llamo "system", que es al resultado del proceso de hardware pegotearle la imagen obtenida por el proceso de software.

Makefile detallado original
Makefile detallado original


Tal como está el Makefile, como que no está fácil, así que lo desarmé para terminar de entenderlo y lo volví a armar. De paso, veamos el proceso pero un poco menos detallado:


Makefile con reglas sencillas
Makefile con reglas sencillas, diagrama simplificado

Eliminé algunas partes del diagrama para recalcar las cuatro reglas y lo de la memoria. 

Arriba a la derecha, se arma la imagen, lo cual incluye tanto nuestro programa como otro código, de esto aún no sé mucho, parece haber algo de inicialización y el de arranque, ya seá desde la RAM o desde flash, este es un punto interesante a explorar, cargar el programa desde memoria SD, agilizaría aún más el proceso.

Arriba a la izquierda, como que estaba confundida la construcción de la imagen con la inclusión de RAM. De hecho, en el Makefile original si modificás el programa regenera sin necesidad el hardware. El primer icebram lo que hace es sólo reservar el espacio de memoria, luego, en system, nuevamente icebram, pone el programa en esa área reservada.

Las nuevas reglas las construí apuntando al final de reglas existentes, los cambios son en el Makefile:


software: BUILD/progmem.hex

Sencillo, pedís "software", hace lo necesario para construir progmem.hex.


hardware: $(ASC_SYN)

según ASC_SYN  = BUILD/$(TOP)_syn.asc viene a ser top_syn.asc en el diagrama.

 

system: $(BIN)

según BIN      = BUILD/$(TOP).bin es top.bin en el diagrama

 

Para romper el encadenamiento erróneo que había, convertí:

$(BLIF) $(JSON): $(YS) $(SRC) BUILD/progmem_syn.hex BUILD/progmem.hex BUILD/defines.sv

en

$(BLIF) $(JSON): $(YS) $(SRC) BUILD/progmem_syn.hex BUILD/defines.sv

Ahora, tal como es de esperar, puedo reemplazar el programa en sólo dos minutos en lugar de los cinco originales.


Respecto a utilizar la flash como arranque, mmh, estuve mirando con más detalle y dice ser SPI, lo cual implica cuatro señales: SCLK (el clock), SS (Slave Select para elegir el chip), MOSI (datos del master al slave) y MISO (datos del slave al master).

Para comenzar, en top.sv parecen estar los dos primeros, pero los últimos están como inout

Me da olor a Dual SPI. Más que luego se instancian uno tales "flash_io" del tipo SB_IO y TRELLIS_IO que según veo por encima rapidito tienen que ver con tristate.

 Luego, en flash.sv, dice

     assign clk_out = clk;

y no me suena que SPI soporte 36Mhz.

¡Qué lástima! Me había hecho ilusiones, incluso le había soldado los pines al sdcard reader que tengo. Esto está muy encima de mis habilidades y no es bloqueante ni aporta mucho al malware que tengo que hacer funcionar...



2021/10/10

H4CK3D 2021: cambios al Makefile

Esquivándole el bulto al problema principal, que es implementar el ataque, sigo introduciendo cambios y mejorías, en este caso al Makefile.

 

Relacionado al Makefile, ya había puesto:

BOARD    ?= edufpga
PROGRAM  ?= hello

Lo primero debido a que desde ciaa/icicle para acá, es la placa que vamos a usar, no quiero estar poniendo BOARD=edufpga en cada llamada a make. Lo segundo es que el foco ha pasado de elegir el BOARD a elegir el PROGRAM.

Tambien había modificado la regla para defines.sv para que tome la configuración de puertos y dispositivos de la carpeta del programa. Esto implica que al hacer un nuevo programa, convienen hacer un make clean para que se reescriba con la nueva configuración.

 

Optimización del programa

 

Una mejoría está relacionada a la optimización del código del programa. Por default grahamedgecombe/icicle lo puso en -Os, que es razonable, hacer el programa lo más chico posible, menos RAM, menos ocupación de la FPGA. Pero para un inexperto como yo en assembly en general y de risc-v en particular, se me hace bastante complicado analizar el código para implementar el ataque, así que ahora existe la opción:

OPTLEVEL ?= -Os

si en la invocación agregás

OPTLEVEL=

como en 

make PROGRAM=access_control OPTLEVEL=""

no optimiza.

 

Uso de azar en PNR


Apenas complicado fué el asunto del SEED. ¿Qué es el SEED? Pues que para el place and route funcione bien, el algoritmo utiliza pseudoazar.

¿Qué es place and route? Es el proceso de ubicar y conectar los componentes en un circuito electrónico en este caso en la FPGA.

¿Qué es pseudoazar? Bueno, primero veamos que es azar. Azar es... tirar los dados. El problema con el azar es que no podés repetirlo y si usás azar para algo, si tenés una falla y no lo podés repetir, se complica diagnosticar la cause. Pero necesitás el azar, ¿en qué quedamos?

El pseudoazar viene a ser tirar los dados un montón de veces y guardar lo que te salió en una lista. Luego, cuando lo necesitás tirás una sola vez los dados para elegir donde empezar en esa lista. Entonces tenés azar y repetición a la vez si necesitás, eligiendo la posición en la lista. Es un azar apto para estas cosas, pero no ante un adversario, pues no bien reusás la lista o comparando ejecuciones, se puede obtener la lista.

La gente que realmente sabe criptografía sabrá ser generosa con mi explicación.

Como el archivo generado depende entonces del pseudoazar, si necesito que dado el mismo diseño obtener el mismo placement and routing, necesito el control de SEED, que en nextprn-ice40 se determina con el parámetro --seed.

Me interesa que genere siempre lo mismo debido a que quizás para el ataque pueda, no creo, hacer la modificación no sobre el código HDL sino en una etapa posterior, veremos.

En el Makefile primero hay de definirlo con vacío como default:

SEED     ?=

y luego en arch/ice40.mk, si SEED está vacío, sigue vacío, pero si hay algo, agregarle la key --speed.

ifeq ($(SEED),)
SEEDVAL =
else
SEEDVAL = --seed $(SEED)
endif

SEEDVAL luego es reemplazado en la línea:

nextpnr-ice40 $(QUIET) --$(SPEED)$(DEVICE) --package $(PACKAGE) $(SEEDVAL) --json $< --pcf $(PCF) --freq $(FREQ_PLL) --asc $@

 

¿Por qué no hice la misma lógica con OPTLEVEL? Buena pregunta... debe ser por que es fácil escribir "" y no "--seed 3".

 

Dependencias

 

El diagrama de Pablo, redibujado así, no me alcanza, necesito más detalle. Para ello necesito corregir un poco el Makefile.

 

icicle flow high level
icicle flow high level


Para comenzar, mover todo objeto temporario o construido a una carpeta, BUILD es un buen lugar, luego, mover los .ys a arch.

De tanto revisar como le fuí tomando la mano y generé un nuevo diagrama:

 

ICE40 flow low level
ICE40 flow low level


 

El problema restante que me queda es el tiempo:

  • yosys: 1:10
  • nextpnr-ice40: 1:45
  • iceprog: 2:00

Tengo que armar reglas de Makefile tal que pueda hacer tener estas cuatro:

  • software: genera progmem.hex.
  • hardware: genera top_syn.asc.
  • system: genera top.bin.
  • all: que haga todo sin tanta sutileza, lo que está ahora.

Será otro día...


 

 



H4CK3D 2021: sigue el desvío, para bien

Esto más que H4CK3D debería llamarse... no sé, queda H4CK3D pues todas estas entradas son lo que no me alcanzaría jamás el tiempo para mostrar en una charla de una conferencia, es toda la investigación previa, la familiarización con el entorno y las herramientas.


Además, se viene la charla y quizás esté un poquito retrasado, eso me pone en modo procrastinador productivo, no hago lo que debería pero si algo útil en su lugar.

 

La excusa de hoy es haber agregado un módulo de servo. ¿Qué es un servo? Sos una de las pocas personas que lee este blog y calculo que deberías estar en sintonía con estos temas, así que si no sabés, podés buscar mejores detalles en Internet, pero te hago un resumen.

 

Es un motorcito con unos engranajes que dependiendo de una señal se posiciona en un ángulo. Esa señal es un pulso que debe cumplir con que el período sea de 20ms y el pulso mida entre 1 y 2 ms.

 

Servo al máximo
Servo al máximo

Servo al mínimo
Servo al mínimo

Esa forma se puede obtener en un microcontrolador con este sencillo programa,


#define PERIOD    0.020
#define MIN_WIDTH 0.001
#define MAX_WIDTH 0.002
float period = PERIOD;
float width = MIN_WIDTH;
int main() {
    DigitalOut servo1(PC_8);   
    while(1) {
       for(int i=0; i< 200; ++i) {
          servo1=1;
          wait(MIN_WIDTH);
          servo1=0;
          wait(PERIOD - MIN_WIDTH);
       }    
       for(int i=0; i< 200; ++i) {
          servo1=1;
          wait(MAX_WIDTH);
          servo1=0;
          wait(PERIOD - MAX_WIDTH);
       }    
    }
} 


La versión completa en  https://os.mbed.com/users/cpantel/code/servo_software/

 

Si ya programaste alguna vez un microcontrolador o si pensás un rato, notarás que no bien quieras hacer algo más, vas a tener tocar los wait()s para compensar, una pesadilla inmanejable. Podrías entonces usar un timer que genere excepciones pero la verdad es que buena parte del los microcontroladores, al menos todos los que me he cruzado, que no son muchos, tiene un dispositivo llamado PWM al que le decís generame pulsos de tales características y lo hacen, aunque hayas logrado colgar al micro.

 

#define PERIOD    0.020
#define MIN_WIDTH 0.001
DigitalIn openButton(D4);
DigitalIn moveUpButton(D5);
DigitalIn moveDownButton(D6);
DigitalIn closeButton(D7);
PwmOut led1(LED1);
PwmOut servo1(PC_8);
PwmOut servo2(PC_9);
float period = PERIOD;
float width = MIN_WIDTH;
int main() {
    servo1.period(PERIOD);
    servo1.pulsewidth(width);
    while (1) {
// hacé lo que quieras acá
    }
}


Listo,un ejemplo más completo que según aprietes los botones mueve el servo en

https://os.mbed.com/users/cpantel/code/servo_PWM/

Volviendo a la cpu risc v en la edu-ciaa-fpga, agregué un PWM restringido, que sólo sirve para manejar un servo. Esto es, con un PWM normal puedo darle por software cualquier frecuencia y duración de pulso, lo cual no necesariamente es correcto. Con el módulo servo, no importa lo que hagas en software, la salida va a ser correcta.


Tuve algunas dificultades, por ejemplo, tengo módulos en los que puedo leer (rtc), en este escribir, pero no pude leer y escribir, ya le dedicaré un rato a ello.

 

La otra, es que tuve que implementar con una condición lógica bastante larga en lugar de unos case que más adelante me hubiesen permitido usar funciones más avanzadas de (system)verilog para generar los casos, ya le dedicaré un rato a ello.

Ya que estaba, aproveché para mejorar los ifdef que habilitan los puertos y los módulos. Antes hubiese sido así:

En top.sv:


`ifdef ARDUINO
    output logic [31:0] arduino,
`endif

y en la instanciación de icicle, en la interfaz:

`ifdef ARDUINO
        .arduino(arduino),
`endif

 

luego en icicle.sv:

 

`ifdef ARDUINO
    //código...

 

El problema es que al hacer el servo, que usa un pin del arduino, tenía que medio hackearle ese pin pues al habilitar ARDUINO, los pines los usaba el módulo arduino. Lo que hice fue separar en _CONN y _DEV, lo cual me permite habilitar los pines del arduino y luego conectarlos al módulo arduino o no instanciar ese módulo y usarlos desde otro.

Esto nos lleva a que cada programa puede usar distintas configuraciones de hardware, asi que además agregué que a los defines.sv que se le copia la base de la placa, se le agreguen lo que el programa pida, entonces en boards/edufpga-defines.sv tengo


// Defines for EDU-FPGA
`define noPMOD0_DEV
`define noPMOD1_DEV
`define noARDUINO_DEV
`define noRTC_DEV
`define noSERVO_DEV
`define noUART_DEV

`define noPMOD0_CONN
`define noPMOD1_CONN
`define noARDUINO_CONN

que cumple la función de documentar que hay disponible, luego en programs/servo_device/edufpga-defines.sv:

// Defines for servo_device running in EDU-FPGA
`define SERVO_DEV
`define UART_DEV

`define ARDUINO_CONN

y esto se arma en el Makefile:

cat boards/$(BOARD)-defines.sv programs/$(PROGRAM)/$(BOARD)-defines.sv > defines.sv

 

En el mismo camino de facilitar cambiar el programa, pasé a un .h el mapa de memoria, actualmente:


#define LEDS        *((volatile uint32_t *) 0x00010000)
#define BUTTONS     *((volatile uint32_t *) 0x00010004)
#define PMOD0       *((volatile uint32_t *) 0x00010008)
#define PMOD1       *((volatile uint32_t *) 0x0001000c)
#define ARDUINO     *((volatile uint32_t *) 0x00010010)
#define RTC         *((volatile uint32_t *) 0x00010014)
#define SERVO       *((volatile uint32_t *) 0x00010018)
#define UART_BAUD   *((volatile uint32_t *) 0x00020000)
#define UART_STATUS *((volatile uint32_t *) 0x00020004)
#define UART_DATA   *((volatile  int32_t *) 0x00020008)
#define MTIME       *((volatile uint64_t *) 0x00030000)
#define MTIMECMP    *((volatile uint64_t *) 0x00030008)

Con todos estos cambios, si algún día alguien quiere portar el programa a otra placa, le va a resultar más fácil.

Esto me llevó, por completitud, a adaptar todo el "legacy", comprobar que todos los programas funcionen, probablemente, con un poco de suerte... https://github.com/cpantel/evilCodeSequence/tree/v0.2.0


Me faltaría documentar un poco en el readme, uno de estos dias...




 

 




2021/09/23

H4CK3D 2021: un desvío innecesario

Recordemos, para la charla necesito comprender como funciona, por una cuestión de completitud y para colaborar con el proyecto edu-ciaa-fpga terminé de incorporar el conector arduino y un pmod como input.

Para el ataque de la charla, viene bien tener un evento de activación, o sea, que no funcione siempre sino en un rango temporal reducido para que sea más difícil que salte en un test, para ello necesito algo que lleve el tiempo, un reloj, independiente del programa, lo llamé ambiciosamente RTC pero es mucho menos, es un trocito de verilog que cuenta los segundos y minutos y desborda a la hora.

¿Por qué no me sirve un reloj por software? pues por que para ello necesitaría software colgado de systick o similar y saber en la RAM donde guarda el valor. Me pareció más sencillo y sin colisión con un escenario real el circuito independiente.

Es muy sencillo:

counter <= counter + 1;
if (counter == COUNT ) begin
  counter <= 0;
  secondsLo <= secondsLo + 1;
  if (secondsLo == 9) begin
    secondsLo <= 0;
    secondsHi <= secondsHi + 1;
    if (secondsHi == 5) begin
      secondsHi <= 0;
      minutesLo <= minutesLo + 1;
      if (minutesLo == 9) begin
        minutesLo <= 0;
        minutesHi <= minutesHi + 1;
        if (minutesHi == 5) begin
          minutesHi <= 0;
        end
      end
    end
  end
end

Te juro que quise tomarlo de uno existente, pero eran muy complicados, quizás no sea una maravilla pero funciona.

En la primera versión tiré eso dentro de un always en el top.sv y lo conecté al arduino, pero era muy sucio y no manifestaba desafío, entonces decidí convertirlo en un módulo y además conectarlo a los buses de datos, direcciones y control como cualquier otro dispositivo.

Como es un módulo, existe en su propio .sv, rtc.sv y en icicle.sv hay que incluirlo:

`include "rtc.sv"

 

Luego hay que agregar el registro donde se lo lee:

logic [31:0] rtc_read_value;

 

y su asignación al registro de lectura:

assign mem_read_value =
       ram_read_value |... | rtc_read_value | ...;

 

Las señales de selección: 

logic rtc_sel;

Las de ready:

logic rtc_ready;
assign mem_ready = ram_ready | ... | rtc_ready | ...;


Mapearlo al direccionamiento:


32'b..001_00000000_000101??: rtc_sel = 1; //0x00010014


Y finalmente a los buses:

 

rtc #(.COUNT(36000000)) rtc (
    .clk_in(clk),
    .reset(reset),
    /* memory bus */
    .address_in(mem_address),
    .sel_in(rtc_sel),
    .read_in(mem_read),
    .read_value_out(rtc_read_value),
    .write_mask_in(mem_write_mask),
    .write_value_in(mem_write_value),
    .ready_out(rtc_ready)
);

 

Puse las señales de escritura tambien, no por copiar y pegar sino por que en el diseño original, en la flash que entiendo no se puede escribir tal como está, están presentes. Intuyo que dado que no se usan no se sintetizan, así que no molestan e igual me queda para el futuro si quisiera cambiar la hora desde el programa.


En el rtc.sv, además de conectar los buses, como la lectura es inmediata:


assign ready_out = sel_in;

Y la lectura proviene de:


assign read_value_out = 

{16'b0,
 sel_in ?
   {minutesHi,minutesLo,secondsHi,secondsLo}
   : 16'b0};


Como usa 16 bits (cuatro para cada dígito), los primeros 16 son ceros. Si está activo sel_in, van los valores apropiados, sino, otros 16 ceros.

Finalmente el programa de ejemplo, lo basé en el hello original para recuperar la uart, el mapeo se hace con:


#define RTC    *((volatile uint32_t *) 0x00010014)


El main lo que hace es leer el RTC, lo copia al conector de arduino para manifestar los valores binarios y 

 

for (;;) {

    uint32_t rtc = RTC;
    ARDUINO = rtc;
    msg[5] = ((rtc & 0xf000 ) >> 12 ) + '0';
    msg[6] = ((rtc & 0xf00 ) >> 8 ) + '0';
    msg[8] = ((rtc & 0xf0 ) >> 4 ) + '0';
    msg[9] = ( rtc & 0xf ) + '0';
    uart_puts(msg);
    LEDS = ~LEDS;
    uint32_t start = rdcycle();
    while ((rdcycle() - start) <= FREQ);
}


Con los msg[x] se parchea un mensaje:

"RTC: ..:..\r\n";


Va tomando de a cuatro bits, los desplaza al nibble menos significativo y lo convierte en el ascii apropiado sumándolo al caracter '0'.


Este mensaje lo tuve que definir con:


    char msg[13];
    msg[0]='R';
    msg[1]='T';
    msg[2]='C';
    msg[3]=':';
    msg[4]=' ';
    msg[5]='.';
    msg[6]='.';
    msg[7]=':';
    msg[8]='.';
    msg[9]='.';
    msg[10]='\r';
    msg[11]='\n';
    msg[12]='\0';

 

En lugar de:

 

char msg[] = "RTC: ..:..\r\n";

 

Debido a este mensaje de error, cuyo significado intuyo pero prefiero no decir nada sin fundamentos para no generar información errónea:

 

/usr/local/lib/gcc/riscv64-unknown-elf/11.1.0/../../../../riscv64-unknown-elf/bin/ld: /usr/local/lib/gcc/riscv64-unknown-elf/11.1.0/../../../../riscv64-unknown-elf/lib/libc.a(lib_a-memcpy.o): ABI is incompatible with that of the selected emulation:
 

target emulation `elf64-littleriscv' does not match `elf32-littleriscv'

 

Funciona ok, el programa toma el valor del RTC y lo pone en el arduino, falta video, creeme y lo muestra en la uart:

 

Minutos y segundos transcurridos desde el inicio
Minutos y segundos transcurridos desde el inicio, reset o último overflow

 







2021/09/20

H4CK3D 2021: un desvío necesario

Si venís leyendo los primeros pasos, entiendo que te va a resultar más o menos natural que siga tomando el control de la placa en lugar de ir directo hacia la POC.

Para esta necesito examinar el flujo de ejecución de las instrucciones del programa a atacar, ante mi se abren tres caminos, no necesariamente excluyentes:


Analizar el código fuente generado

 

O tomar el ejecutable y decompilarlo a assembly o pedirle al compilador que genere o conserve el mismo código. Esto último se logra con la opción -save-temps agregada a la siguiente línea del Makefile:


CFLAGS = -march=rv32i -mabi=ilp32 -Wall -Wextra -pedantic -DFREQ=$(FREQ_PLL)000000 -Os -ffreestanding -nostartfiles -g -Iprograms/$(PROGRAM)

Me parece conveniente además quitár ese -Os, que es optimización de tamaño.

 

Generar una simulación

 

Por lo que entendí de la charla FOSS para el desarrollo con FPGA de Rodrigo Melo, verilator es mi amigo, pero fuí a la documentación y está pensada para quien se va a dedicar, no para un turista como yo, aprender a usarlo es un subproyecto tan grande como el resto del proyecto.

 

Instrumentación

 

Puedo volcar los buses a los puertos de salida y capturarlos desde otro sistema, puede ser buen momento para usar la PYNQ, que tiene la cantidad de entradas apropiadas y al correr linux me facilita muchas cosas, pero, también es un subproyecto muy grande.


Tanto en la simulación como en la monitorización, necesito lidiar con:

static inline uint32_t rdcycle(void) {
    uint32_t cycle;
    asm volatile ("rdcycle %0" : "=r"(cycle));
    return cycle;
}

        uint32_t start = rdcycle();
        while ((rdcycle() - start) <= FREQ);

 

No sé si es un mero delay o cumple otra función indispensable, recordemos que cualquier problema de concurrencia se resuelve con un delay().

Mientras barajo todas estas posibilidades, lo mejor es terminar de implementar el acceso al conector arduino y el pmod restante, a modo de práctica y profundización del conocimiento del sistema y de paso es trabajo que se puede aportar al icicle de Pablo, por si alguien luego lo quiere aprovechar.


entonces...

 

De tanto repetir, ya tengo un procedimiento, funciona pero los razonamientos que agrego son muy personales, tomalos con suma precaución:

Identificar los pines y agregarlos al archivo.pcf

Agregar esos pines ya sea como input u ouput al top.

Pasarlos al módulo icicle

Dentro del modulo icicle, tomar los pines

Para cada módulo definir

    logic XXX_sel; 

Este sirve para que luego el mapeador, el casez (mem_address) identique a donde está apuntando la dirección en el bus 

    logic [31:0] XXX_read_value;

Es donde va a quedar escrito el valor leído

    logic XXX_ready;

Avisa que el valor leído puede ser leído. Fijate que casi todos los _ready están conectados a _sel, esto es por que se pueden leer inmediatamente. En los casos de  ram.sv y flash.sv evidentemente hay demoras.

Si notaste que los dispositivos están enmarcado en unos `ifdef asociados a defines que van en edufpga-defines.sv:

 

// Defines for EDU-FPGA
`define PMOD0
`define PMOD1
`define ARDUINO

 

Notarás tambien que en el caso de no estar definido se le asigna cero a la lectura y siempre listo:

 

`ifdef PMOD1
    assign pmod1_read_value = {24'b0, pmod1_sel ? pmod1 : 8'b0};
    assign pmod1_ready = pmod1_sel;
`else
    assign pmod1_read_value = 0;
    assign pmod1_ready = pmod1_sel;
`endif

Como programador me asusta la línea repetida, pero me parece más clara, así que ahí queda por ahora.

Cuando si está definido, como son los 8 bits menos significativos, se le ponen ceros. Luego, si ha sido seleccionado el valor de los pines, si no, ceros.


Fijate que ese XXX_read_value va a parar a un bruto OR, por eso la asignación debe estar condicionada:

assign mem_read_value = ram_read_value | leds_read_value | buttons_read_value | pmod0_read_value | pmod1_read_value | arduino_read_value | uart_read_value | timer_read_value | flash_read_value;
 

Parecido los XXX_ready:

assign mem_ready = ram_ready | leds_ready | buttons_ready | pmod0_ready | pmod1_ready | arduino_ready | uart_ready | timer_ready | flash_ready | mem_fault;

Luego, hay que copiar/pegar/editar leds o buttons según quieras leer o escribir.

En el caso de arduino con más de 8 bits, hay que implementar los cuatro ifs, es por que... mmh, luego voy a investigarlo mejor, me parece que se puede simplificar, va como tarea al backlog que tengo conectado a /dev/null.

Creo que finalmente, hay que mapear:

La idea sería algo como (quité algunos ceros para que no salte la línea):

32'b..._00000001_00000000_000011??: pmod1_sel   = 1; // 0x0001000c
32'b..._00000001_00000000_000100??: arduino_sel = 1; // 0x00010010

Esos dos ?? son dos bits, cuatro posiciones. Para los pmods sobra, para el arduino está ok.

Para 0x0001000c no hay nada en:

0x0001000d
0x0001000e
0x0001000f

En cambio para arduino, se usan los cuatro bytes (32 bits):

0x00010010
0x00010011
0x00010012
0x00010013

 

Luego en el programa:

#define PMOD1       *((volatile uint32_t *) 0x0001000c)
#define ARDUINO     *((volatile uint32_t *) 0x00010010)

En la carpeta programs hay... programas, ¿qué esperabas? El ejemplo más completo es buttons2ledsArduinoShiftPmodToPmod que no hace falta que te explique que los botones que aprietes se van a manifestar en los leds y que lo que pongas en un pmod saldrá por el otro y que en el conector arduino verás unos bits moviéndose.

 

El shift en el conector arduino
El shift en el conector arduino

El shift es medio raro por como armé el pinout, va en zig-zag. Y el pin 89 estába mal etiquetado, es 82, ya avisé.

 


 

Los switches que uso para el pmod tienen décadas, no andan del todo bien...

 

Si mirás el código en icicle.sv, verás que para leds y buttons estoy usando los parámetros LEDCOUNT y BUTTONCOUNT respectivamente, no así parámetros para los pmods ni arduino. Esto es por que no espero que tengan valores variables. De hecho, esos parámetros son una gentileza para quien quiera adaptar a las otras placas, la edu-ciaa-fpga tiene 4 y 4, listo.

Pude haber hecho bloques de 8 bits o 4 y así combinar inputs con outputs con facilidad y mejor aún llevar el concepto a dos módulos, ponele GPInput y GPOutput, o mucho mejor implementar el módulo GPIO, pero eso me desvía de mi proyecto, yo voy a usar los pines directamente, el archivo de defines lo voy a tener vacío. No quiero ni necesito que haya nada mapeado a la memoria ni que sea accesible desde el programa en ejecución.


El módulo GPIO

 

Este debería tener al menos una máscara de selección de sentido, quizás otra de enable/disable, lo mejor que se me ocurre es agrandar la distancia que hay en el casez (mem_address) entre direcciones y que luego el módulo termine de determinar si la interacción es con las máscaras o con los registros de datos. 

Desde el momento que no hay interrupciones, no vale la pena preocuparse por ellas.

Ya tengo un branch local con el código para pines inout, algún dia...

 

Conclusiones

 

He usado variables desde programas en C, ¿quién no? He usado funciones o macros provistas por el fabricante para configurar y acceder los puertos, magia. He visto que Vivado cuando le dás un dispositivo para usar desde la PYNQ te define direcciones de memoria donde va a estar. (dos seriales, pynq con acelerador, acelerador con serial, acelerador con leak).

Ahora he mapeado dispositivos a posiciones de memoria y lo he hecho de modo incremental.

Para ello, no me he tenido que preocupar mucho por el legacy, son cuatro programas.

Tampoco por unos bytes más o menos ni por el silicio que consuman.

Este proyecto es muy recomendable para poder comprender realmente estos conceptos y porqué a veces los registros están con un orden aparentemente razonable y otra veces no. Lo que te propongo es que te bajes el repo:

git clone https://github.com/ciaa/icicle.git

y te pares donde tomé yo:

git checkout f63f218

y te diviertas reconstruyendo mi camino o tomando el tuyo propio.


Uno de estos días cuando Pablo acepte mi segundo pull request, verás todo. O podés hacer trampa.