¿Cómo se implementa la compilación de depuración en VHDL?

4

Vengo de fondo C y me están introduciendo a VHDL. Leí sobre la sintaxis y la concurrencia / consecutividad de las acciones.

Ahora me pregunto cómo se implementan las características de solo desarrollo. Cosas como assert() y #ifndef NDEBUG LOG_MSG_UART("99 bottles of beer") .

El escenario, en el que estoy pensando, es este. Tengamos una placa de desarrollo con un FPGA y varios encabezados de depuración. Varias señales se envían a esos encabezados de depuración para medición con un osciloscopio o analizador lógico. En la versión de lanzamiento, no habrá tales encabezados. ¿Cómo se escribe el código para que puedan cambiar fácilmente entre las versiones de lanzamiento y depuración?

Un ejemplo idiomático ayudaría mucho.

    
pregunta Vorac

2 respuestas

4

En VHDL, la instrucción ASSERT básica se usa para asegurar que cuando estás simulando, atrapas todas esas condiciones. La simulación es un paso del proceso de depuración, que es más parecido a un software, ya que tiene acceso a todo dentro del diseño.

También puede (con acceso a herramientas de un calibre suficientemente alto) usar PSL para escribir aserciones más complejas que pueden implican tiempos así como condicionales simples.

Una vez que haya simulado a su satisfacción, si necesita depurar el hardware real, puede usar las siguientes herramientas:

  • analizador lógico integrado (Xilinx: Chipscope, Altera: SignalTap): lo crea en el momento de la compilación y luego puede activar y monitorear las señales. Las señales se capturan en tiempo real a la memoria interna, por lo que hay un límite de cuántas y por cuánto tiempo, pero proporciona una excelente visibilidad. Sin embargo, puede llevar mucho tiempo, ya que cada vez que cambie de opinión acerca de la configuración, tendrá que reconstruir el FPGA (que es un proceso medido en decenas de minutos o incluso horas, dependiendo de la complejidad del diseño).
  • pines de repuesto - con o sin LED. Saca señales internas críticas y monitoréalas con un osciloscopio y un analizador lógico. Una vez más, reconstruye cada vez que lo cambies. Esto también se puede hacer de forma más dinámica en Xilinx-land con el editor de FPGA, por lo que puede hacer actualizaciones relativamente rápidas al flujo de bits, ¡si puede encontrar la señal que desea dentro de la lista de redes optimizada!

Como señaló Brian, también necesitará un osciloscopio (y potencialmente un analizador lógico) para depurar las interfaces externas, verificando la sincronización, etc.

Con respecto al tema de tener compilaciones "debug" y "release", eso no se maneja de una manera "estándar" (a diferencia del mundo del software). El comportamiento no sería muy diferente en la mayoría de los casos: como el FPGA es muy determinista, no pierde velocidad al tener lógica de depuración allí (o si lo hace, aún cumple con las especificaciones requeridas, ya que de lo contrario no podría depurar) ¡el chip!). Terminará con más lógica de la que necesita, pero nuevamente, tiene que encajar en el dispositivo que tiene (a menos que esté planeando reducir el tamaño para el lanzamiento, pero dado los incrementos en el tamaño del dispositivo, necesitará un lote de lógica de depuración para que valga la pena!).

También tenga en cuenta que (por lo que he oído) nadie compila con diferentes configuraciones de optimizador para depuración en comparación con la versión, lo que puede dificultar la búsqueda de las señales que desea, por lo que debe agregar attribute s para evitar que estén tan optimizados como lo estarían, lo que es bueno ya que el resto del diseño se optimiza. No tan bueno que tienes que reconstruir para cambiar los atributos. (Al menos hay mucho tiempo para mantener su documentación actualizada mientras espera las compilaciones :)

Si quieres hacer esto, te sugiero un par de maneras:

  • Tenga un generic booleano en la instancia de nivel superior: puede deshabilitar la depuración de forma predeterminada y puede anularlo en la línea de comandos / desde la GUI de su sintetizador.
  • Tenga un constant booleano en un paquete que use en su código principal: seleccione entre los dos modos editando este archivo

Con la primera opción, solo el nivel superior puede ver el estado del estado de depuración (a menos que lo pase a la jerarquía, ¡pero eso puede volverse desagradable rápidamente, ya que generalmente también es necesario pasar las señales de depuración! ).

Con el segundo, cualquier entidad puede obtener una constante de depuración y utilizarla; de nuevo, es necesario pasar las señales hacia arriba en el árbol.

Puedes usar el booleano en un if..generate..else generate para crear una instancia de la lógica que deseas (o no quieres) según el "modo".

    
respondido por el Martin Thompson
7

Probablemente más fácilmente.

Obtienes el gran parte del diseño en simulación antes de pasar al hardware .

Utilice bancos de pruebas de autocomprobación (o técnicas de verificación de orden superior, incluidas PSL, pruebas aleatorias restringidas o OSVVM ) en la función de pruebas unitarias para verificar Que el bloque cumpla con su funcionalidad básica.

Cuando no lo hace, el simulador tiene un visor de forma de onda que puede mostrarle el valor de cada señal en cada momento, mostrando de forma útil incógnitas ("X" o "Z", por ejemplo) en rojo. Use esto juiciosamente para trabajar desde los resultados incorrectos hasta su causa; edite, compile, vuelva a simular, etc. hasta que el bloque haga lo que usted quiere.

Los bancos de prueba de autocomprobación pueden comparar el diseño de bajo nivel (sintetizable) con un modelo algorítmico de nivel superior: cualquier algoritmo básico que puede escribir en C también puede escribirse en VHDL (¡pero puede que no sea sintetizable!) o el banco de pruebas puede leer los resultados esperados de un archivo. Los bancos de pruebas son, por lo tanto, sólo para el desarrollo. (Generalmente, no tienen acceso a las entrañas de un bloque de hardware; solo son entradas y salidas, por lo que son probadores de caja negra). Pero un banco de pruebas puede conectar varias cajas negras para probar cómo interactúan.

Entonces, compare los resultados reales de un bloque con los resultados esperados (es posible que necesite almacenar los resultados esperados hasta que estén listos). Esto normalmente se hace con una declaración de afirmación:

VHDL ayuda mucho gracias a tener un sistema de tipo decente: ¡úselo!
(O frustrado luchando contra él: ¡tu elección!)

Mal VHDL:

addr : integer;
...
assert addr >= 0 report "Address arithmetic overflow" severity FAILURE;

Mejor VHDL:

subtype address is natural range 0 to 2**16 - 1;
addr : address;

La afirmación ahora es redundante porque el simulador lo hace por usted: los valores fuera del rango especificado están atrapados. (La síntesis aún puede generar desbordamientos; las herramientas de sintetizadores pueden asumir que ya tiene el diseño correcto. Por lo tanto, no desperdiciarán el hardware en las comprobaciones de desbordamiento a menos que las codifique explícitamente)

Puedes hacer muchas cosas buenas con el sistema de tipos: esto debería ser una segunda naturaleza para los desarrolladores de SW, pero parece que ya no se enseña. Un par de ejemplos:

   variable mem : array(addr) of integer;

   for i in addr loop
      mem(i) := 0;
   end loop;

El tamaño de la matriz y los límites de bucle definidos por el tipo: sin errores de bucle o sobrecargas de búfer.

assert addr'high + 1 = 65536 report "Wrong address size" severity failure;
Los atributos

('alto,' bajo, 'rango (define un subtipo entero), etc., permiten la introspección en tiempo de ejecución del tipo (establecer límites de bucle para una matriz sin restricciones, etc.) o como aquí para verificar las suposiciones sobre un tipo declarado en otro lugar. ..

type colour is (red, green, blue); 

un poco como una enumeración de C excepto ...

type RGB is array(colour) of real;
constant gain : RGB := (red => 0.31, green => 0.58, blue => 0.11);
signal YUV,RGB_in : RGB;
...
for c in colour loop
   YUV(c) <= RGB_in(c) * gain(c);
end loop;

habiendo definido un tipo de este tipo, puede usarlo como un índice de matriz o un bucle sobre él, etc. Y así sucesivamente.

La asociación con nombre para argumentos, componentes de matriz, etc. reduce errores y aclara el código.

Los paquetes ... reúnen los tipos y sus operaciones en componentes reutilizables seguros. Y así sucesivamente ...

Mis diseños tienden a tener un paquete "común" que define cosas ampliamente utilizadas como "dirección" arriba: todo lo usa y si cambio el tamaño de la dirección, todos los bucles se reajustan al nuevo tamaño, y cualquier cosa con supuestos codificados debería caer en una aserción (como se indica arriba) hasta que lo arregle ...

    
respondido por el Brian Drummond

Lea otras preguntas en las etiquetas