Python y FPGA. Prueba

Como continuaci贸n del primer art铆culo , quiero mostrar un ejemplo de trabajo con FPGA (FPGA) en python como ejemplo. Este art铆culo cubrir谩 el aspecto de la prueba con m谩s detalle. Si el marco MyHDL permite a las personas que trabajan en Python, utilizando la sintaxis y el ecosistema familiares, mirar el mundo de FPGA, entonces los desarrolladores de FPGA experimentados no entienden el significado del uso de Python. Los paradigmas de descripci贸n de hardware para MyHDL y Verilog son similares, y elegir un idioma espec铆fico es una cuesti贸n de h谩bito y gusto. Verilog / VHDL representa el hecho de que el firmware se ha escrito en estos idiomas durante mucho tiempo y, de hecho, son est谩ndar para la descripci贸n de equipos digitales. Python, como novato en este campo, puede competir en entornos de prueba de escritura. Una parte importante del tiempo del desarrollador de FPGA se dedica a probar sus dise帽os. A continuaci贸n, quiero demostrar con un ejemplo c贸mo se hace esto en python con MyHDL.

Supongamos que hay una tarea para describir un dispositivo que funciona con memoria en el FPGA. Para simplificar, tomar茅 la memoria que se comunica con otros dispositivos a trav茅s de una interfaz paralela (y no a trav茅s de una serie, por ejemplo, I2C). Dichos microcircuitos no siempre son pr谩cticos en vista del hecho de que se requieren muchos pines para trabajar con ellos; por otro lado, se proporciona un intercambio de informaci贸n m谩s r谩pido y f谩cil. Por ejemplo, dom茅stico 1645RU1U y sus an谩logos.



Descripci贸n del m贸dulo


El registro se ve as铆: FPGA proporciona una direcci贸n de celda de 16 bits, datos de 8 bits, genera una se帽al de escritura WE (habilitaci贸n de escritura). Dado que OE (habilitaci贸n de salida) y CE (habilitaci贸n de chip) siempre est谩n habilitados, la lectura se produce cuando se cambia la direcci贸n de la celda. La escritura y la lectura se pueden llevar a cabo de forma secuencial en varias celdas seguidas, comenzando desde una direcci贸n adr_start espec铆fica, registrada en el borde delantero de la se帽al adr_write, y una celda en una direcci贸n arbitraria (acceso aleatorio).

En MyHDL, el c贸digo se ve as铆 (las se帽ales de escritura y lectura vienen en l贸gica inversa):

from myhdl import * @block def ram_driver(data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we): #     mem_z = data_memory.driver() #      @always(adr_write.posedge, write.posedge, read.negedge) def write_start_adr(): if adr_write: #    adr.next = adr_start else: #    / adr.next = adr + 1 @always(write) def write_data(): if not write: mem_z.next = data_in we.next = 0 #    ,    else: mem_z.next = None #        data_out.next = data_memory we.next = 1 return write_data, write_start_adr 

Si se convierte a Verilog con la funci贸n:

 def convert(hdl): data_memory = TristateSignal(intbv(0)[8:]) data_in = Signal(intbv(0)[8:]) data_out = Signal(intbv(0)[8:]) adr = Signal(intbv(0)[16:]) adr_start = Signal(intbv(0)[16:]) adr_write = Signal(bool(0)) read, write, we = [Signal(bool(1)) for i in range(3)] inst = ram_driver(data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we) inst.convert(hdl=hdl) convert(hdl='Verilog') 

entonces obtenemos lo siguiente:
 `timescale 1ns/10ps module ram_driver ( data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we ); input [7:0] data_in; output [7:0] data_out; reg [7:0] data_out; output [15:0] adr; reg [15:0] adr; input [15:0] adr_start; input adr_write; inout [7:0] data_memory; wire [7:0] data_memory; input read; input write; output we; reg we; reg [7:0] mem_z; assign data_memory = mem_z; always @(write) begin: RAM_DRIVER_WRITE_DATA if ((!write)) begin mem_z <= data_in; we <= 0; end else begin mem_z <= 'bz; data_out <= data_memory; we <= 1; end end always @(posedge adr_write, posedge write, negedge read) begin: RAM_DRIVER_WRITE_START_ADR if (adr_write) begin adr <= adr_start; end else begin adr <= (adr + 1); end end endmodule 

No es necesario convertir un proyecto a Verilog para simulaci贸n, este paso ser谩 necesario para flashear el FPGA.

Modelado


Despu茅s de la descripci贸n de la l贸gica, el proyecto debe ser verificado. Puede restringirse, por ejemplo, para simular influencias de entrada y ver la respuesta del m贸dulo en el diagrama de tiempo. Pero con esta opci贸n, es m谩s dif铆cil predecir la interacci贸n de su m贸dulo con un chip de memoria. Por lo tanto, para verificar completamente el funcionamiento del dispositivo creado, debe crear un modelo de memoria y probar la interacci贸n entre estos dos dispositivos.

Dado que el trabajo se lleva a cabo en python, el tipo de diccionario dado (diccionario) se sugiere para el modelo de memoria. Los datos en los que se almacenan como {clave: valor}, y para este caso {direcci贸n: datos}.

 memory = { 0: 123, 1: 456, 2: 789 } memory[0] >> 123 memory[1] >> 456 

Para el mismo prop贸sito, el tipo de datos de la lista es adecuado, donde cada elemento tiene sus propias coordenadas, lo que indica la ubicaci贸n del elemento en la lista:

 memory = [123, 456, 789] memory[0] >> 123 memory[1] >> 456 

El uso de diccionarios para simular la memoria parece preferible en vista de una mayor visibilidad.

La descripci贸n del shell de prueba (en el archivo test_seq_access.py) comienza con la declaraci贸n de se帽ales, la inicializaci贸n de los estados iniciales y su lanzamiento a la funci贸n del controlador de memoria descrita anteriormente:

 @block def testbench(): data_memory = TristateSignal(intbv(0)[8:]) data_in = Signal(intbv(0)[8:]) data_out = Signal(intbv(0)[8:]) adr = Signal(intbv(0)[16:]) adr_start = Signal(intbv(20)[16:]) adr_write = Signal(bool(0)) read, write, we = [Signal(bool(1)) for i in range(3)] ram = ram_driver(data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we) 

A continuaci贸n se describe el modelo de memoria. Los estados iniciales se inicializan, por defecto la memoria se llena con valores cero. Limite el modelo de memoria a 128 celdas:

 memory = {i: intbv(0) for i in range(128)} 

y describa el comportamiento de la memoria: cuando WE en el estado bajo, escriba el valor en la l铆nea en la direcci贸n de memoria correspondiente; de 鈥嬧媗o contrario, el modelo da el valor en la direcci贸n dada:

 mem_z = data_memory.driver() @always_comb def access(): if not we: memory[int(adr.val)] = data_memory.val if we: data_out.next = memory[int(adr.val)] mem_z.next = None 

Luego, en la misma funci贸n, puede describir el comportamiento de las se帽ales de entrada (para el caso de escritura / lectura secuencial): se registra la direcci贸n de inicio 鈫 se registran 8 celdas de informaci贸n 鈫 se registra la direcci贸n de inicio 鈫 se leen 8 celdas de informaci贸n grabadas.

 @instance def stimul(): init_adr = random.randint(0, 50) #   yield delay(100) write.next = 1 adr_write.next = 1 adr_start.next = init_adr #   yield delay(100) adr_write.next = 0 yield delay(100) for i in range(8): # 8    write.next = 0 data_in.next = random.randint(0, 100) yield delay(100) write.next = 1 yield delay(100) adr_start.next = init_adr #   adr_write.next = 1 yield delay(100) adr_write.next = 0 yield delay(100) for i in range(8): #   read.next = 0 yield delay(100) read.next = 1 yield delay(100) raise StopSimulation return stimul, ram, access 

Ejecutar simulaci贸n:

 tb = testbench() tb.config_sim(trace=True) tb.run_sim() 

Despu茅s de iniciar el programa, el archivo testbench_seq_access.vcd se genera en la carpeta de trabajo, 谩bralo en gtkwave:

 gtkwave testbench_seq_access.vcd 

Y vemos la imagen:



La informaci贸n registrada fue le铆da con 茅xito.

Puede ver el contenido de la memoria agregando el siguiente c贸digo a testbench:

 for key, value in memory.items(): print('adr:{}'.format(key), 'data:{}'.format(value)) 

Lo siguiente aparece en la consola:



Prueba


Despu茅s de eso, puede realizar varias pruebas automatizadas con un mayor n煤mero de celdas grabables / legibles. Para hacer esto, se agregan varios ciclos de prueba y diccionarios ficticios a testbench, donde se agrega la informaci贸n escrita y legible y la construcci贸n de aserci贸n, lo que provoca un error si dos diccionarios no son iguales:

 @instance def stimul(): for time in range(100): temp_mem_write = {} temp_mem_read = {} init_adr = random.randint(0, 50) yield delay(100) write.next = 1 adr_write.next = 1 adr_start.next = init_adr yield delay(100) adr_write.next = 0 yield delay(100) for i in range(64): write.next = 0 data_in.next = random.randint(0, 100) temp_mem_write[i] = int(data_in.next) yield delay(100) write.next = 1 yield delay(100) adr_start.next = init_adr adr_write.next = 1 yield delay(100) adr_write.next = 0 yield delay(100) for i in range(64): read.next = 0 temp_mem_read[i] = int(data_out.val) yield delay(100) read.next = 1 yield delay(100) assert temp_mem_write == temp_mem_read, "   " for key, value in memory.items(): print('adr:{}'.format(key), 'data:{}'.format(value)) raise StopSimulation return stimul, ram, access 

A continuaci贸n, puede crear un segundo banco de pruebas para probar la operaci贸n en modo de acceso aleatorio: test_random_access.py.

La idea de la segunda prueba es similar: escribimos informaci贸n aleatoria en una direcci贸n aleatoria y agregamos un par {direcci贸n: datos} al diccionario temp_mem_write. Luego damos la vuelta a las direcciones en este diccionario y leemos la informaci贸n de la memoria, ingres谩ndola en el diccionario temp_mem_read. Y al final con la construcci贸n de aserci贸n, verificamos el contenido de dos diccionarios.

 import random from myhdl import * from ram_driver import ram_driver @block def testbench_random_access(): data_memory = TristateSignal(intbv(0)[8:]) data_in = Signal(intbv(0)[8:]) data_out = Signal(intbv(0)[8:]) adr = Signal(intbv(0)[16:]) adr_start = Signal(intbv(20)[16:]) adr_write = Signal(bool(0)) read, write, we = [Signal(bool(1)) for i in range(3)] ram = ram_driver(data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we) memory ={i:intbv(0) for i in range(128)} mem_z = data_memory.driver() @always_comb def access(): if not we: memory[int(adr.val)] = data_memory.val if we: data_out.next = memory[int(adr.val)] mem_z.next = None @instance def stimul(): for time in range(10): temp_mem_write = {} temp_mem_read = {} yield delay(100) for i in range(64): write.next = 1 adr_write.next = 1 adr_start.next = random.randint(0, 126) yield delay(100) adr_write.next = 0 yield delay(100) write.next = 0 data_in.next = random.randint(0, 100) temp_mem_write[int(adr_start.val)] = int(data_in.next) yield delay(100) write.next = 1 yield delay(100) for key in temp_mem_write.keys(): adr_start.next = key adr_write.next = 1 yield delay(100) adr_write.next = 0 yield delay(100) read.next = 0 temp_mem_read[key] = int(data_out.val) yield delay(100) read.next = 1 yield delay(100) assert temp_mem_write == temp_mem_read, '  random access' raise StopSimulation return stimul, ram, access tb = testbench_random_access() tb.config_sim(trace=True) tb.run_sim() 

Python tiene varios marcos para automatizar la ejecuci贸n de pruebas. Tomar茅 pytest por simplicidad, debe instalarse desde pip:

 pip3 install pytest 

Cuando se inicia el comando "pysest" desde la consola, el marco buscar谩 y ejecutar谩 todos los archivos en la carpeta de trabajo con "test_ *" en sus nombres.



Pruebas completadas con 茅xito. Cometer茅 un error deliberadamente en la descripci贸n del dispositivo:

 @block def ram_driver(data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we): mem_z = data_memory.driver() @always(adr_write.posedge, write.posedge, read.negedge) def write_start_adr(): if adr_write: adr.next = adr_start else: adr.next = adr + 1 @always(write) def write_data(): if not write: mem_z.next = data_in we.next = 1 #  ,    else: mem_z.next = None data_out.next = data_memory we.next = 1 

Corro pruebas:



Como se esperaba, en ambas pruebas, se consider贸 la informaci贸n inicial (ceros), es decir, no se registr贸 nueva informaci贸n.

Conclusi贸n


El uso de python junto con myHDL le permite automatizar las pruebas de firmware desarrollado para FPGA y crear casi cualquier entorno de prueba usando las capacidades ricas del lenguaje de programaci贸n python.

El art铆culo considera:

  • creando un m贸dulo que funciona con memoria;
  • creando un modelo de memoria;
  • creaci贸n de casos de prueba;
  • prueba de automatizaci贸n con el marco pytest.

Source: https://habr.com/ru/post/442010/


All Articles