Como se explicó en la introducción, existen varias razones por las cuales podemos vernos beneficiados en utilizar archivos de donde tomar nuestros estímulos. Los mismos pueden provenir de nuestro modelo de referencia escrito en un lenguaje de alto nivel. Por otro lado es también muy útil dejar la tarea de verificación de los resultados a herramientas exteriores. En este último caso necesitaremos guardar los resultados obtenidos en un archivo.
Ilustraremos con ejemplos muy simples varias situaciones posibles. Usaremos los componentes que ya hemos mostrado. Queda en claro que se trata de ejemplos sólo ilustrativos, en los que se usa una técnica mucho más compleja que la que verdaderamente es necesaria.
En este ejemplo supondremos que la obtención de los valores de referencia es compleja y que conviene realizarla usando una implementación externa. Como circuito de prueba usaremos nuestro Ejemplo 5-1.
Como modelo de referencia usaremos una implementación en lenguaje C de dicho conversor, una posible implementación se encuentra en el Ejemplo 5-9.
Ejemplo 5-9. Implementación en lenguaje C de un generador de código Aiken (gen_aiken.c)
#include <stdio.h> void Binario(int v, FILE *f) { int mask=8; for (; mask; mask>>=1) putc(v & mask ? '1' : '0',f); putc('\n',f); } int main() { int i; FILE *f=fopen("aiken.dat","wt"); for (i=0; i<10; i++) Binario(i<5 ? i : i+6,f); for (; i<16; i++) fputs("XXXX\n",f); return 0; }
La salida de este programa se guarda en un archivo denominado aiken.dat. El contenido se muestra en el Ejemplo 5-10.
Ejemplo 5-10. Código Aiken generado (aiken.dat)
0000 0001 0010 0011 0100 1011 1100 1101 1110 1111 XXXX XXXX XXXX XXXX XXXX XXXX
En el Ejemplo 5-11 podemos observar la implementación de un test bench que utiliza el contenido del archivo aiken.dat para verificar nuestro conversor.
Ejemplo 5-11. Valores de referencia desde un archivo (t_b2a_4.vhdl)
library IEEE; use IEEE.std_logic_1164.all; use IEEE.numeric_std.all; library std; use std.textio.all; entity T_B2A_4 is end entity T_B2A_4; architecture Simulador of T_B2A_4 is -- Declaración del componente a probar component BCD2Aiken is port( A : in std_logic_vector(3 downto 0); B : out std_logic_vector(3 downto 0)); end component BCD2Aiken; -- Funciones auxiliares procedure str2sv(s: in string; sv: out std_logic_vector) is variable i : integer; begin for i in s'range loop if s(i)='U' then sv(i-1):='U'; elsif s(i)='X' then sv(i-1):='X'; elsif s(i)='0' then sv(i-1):='0'; elsif s(i)='1' then sv(i-1):='1'; elsif s(i)='Z' then sv(i-1):='Z'; elsif s(i)='W' then sv(i-1):='W'; elsif s(i)='L' then sv(i-1):='L'; elsif s(i)='H' then sv(i-1):='H'; elsif s(i)='-' then sv(i-1):='-'; else report "Elemento desconocido" severity failure; end if; end loop; end procedure str2sv; procedure read(l: inout line; v: out std_logic_vector) is variable s : string(v'length downto 1); begin read(l,s); str2sv(s,v); end procedure read; -- Identificador para el archivo de datos file f : text; -- Señales auxiliares signal entrada : std_logic_vector(3 downto 0):="0000"; signal salida : std_logic_vector(3 downto 0); begin -- Instancia del dispositivo a probar DUT : BCD2Aiken port map(A => entrada, B => salida); -- Proceso secuencial que realiza las pruebas pruebas: process variable valor : std_logic_vector(3 downto 0); variable l : line; variable status : file_open_status; begin report "Probando el conversor de BCD a Aiken" severity note; -- Abrimos el archivo file_open(status,f,"aiken.dat",read_mode); assert status=open_ok report "No se pudo abrir aiken.dat" severity failure; while not(endfile(f)) loop readline(f,l); read(l,valor); wait for 1 ns; assert salida=valor report "Falla para "& integer'image(to_integer(unsigned(entrada))) severity failure; entrada <= std_logic_vector(unsigned(entrada)+1); end loop; -- Cerramos el archivo file_close(f); report "Prueba exitosa!" severity note; wait; end process pruebas; end architecture Simulador; -- Entity: T_B2A_4
Cabe destacar que podríamos verificar cualquier otro tipo de conversor de cuatro bits con sólo cambiar el contenido del archivo de datos y el nombre del componente a verificar. Por lo que, el proceso es más complejo, pero también es mucho más versatil.
Cuando los estímulos que deseamos aplicar son complejos puede ser conveniente generarlos externamente. Es muy frecuente que el archivo de verificación contenga los estímulos y el resultado esperado. Otra posibilidad es que sean dos archivos separados.
Siguiendo con el ejemplo anterior del conversor de BCD natural a Aiken, ilustraremos un programa generador que incluya los estímulos y un test bench acorde con el mismo.
Una posible implementación del programa generador se puede observar en el Ejemplo 5-12.
Ejemplo 5-12. Implementación en lenguaje C de un generador de código Aiken, incluyendo el estímulo (gen_aiken_est.vhdl)
#include <stdio.h> void Binario(int v, FILE *f) { int mask=8; for (; mask; mask>>=1) putc(v & mask ? '1' : '0',f); } int main() { int i; FILE *f=fopen("aiken_est.dat","wt"); for (i=0; i<10; i++) { Binario(i,f); putc(' ',f); Binario(i<5 ? i : i+6,f); putc('\n',f); } for (; i<16; i++) { Binario(i,f); fputs(" XXXX\n",f); } return 0; }
La salida de este programa se guarda en un archivo denominado aiken_est.dat, cuyo contenido se muestra en el Ejemplo 5-13.
Ejemplo 5-13. Código Aiken generado, incluyendo el estímulo (aiken_est.dat)
0000 0000 0001 0001 0010 0010 0011 0011 0100 0100 0101 1011 0110 1100 0111 1101 1000 1110 1001 1111 1010 XXXX 1011 XXXX 1100 XXXX 1101 XXXX 1110 XXXX 1111 XXXX
En el Ejemplo 5-14 podemos observar la implementación de un test bench que utiliza el contenido del archivo aiken_est.dat para verificar nuestro conversor.
Ejemplo 5-14. Estímulos y valores de referencia desde un archivo (t_b2a_5.vhdl)
library IEEE; use IEEE.std_logic_1164.all; use IEEE.numeric_std.all; library std; use std.textio.all; entity T_B2A_5 is end entity T_B2A_5; architecture Simulador of T_B2A_5 is -- Declaración del componente a probar component BCD2Aiken is port( A : in std_logic_vector(3 downto 0); B : out std_logic_vector(3 downto 0)); end component BCD2Aiken; -- Funciones auxiliares procedure str2sv(s: in string; sv: out std_logic_vector) is variable i : integer; begin for i in s'range loop if s(i)='U' then sv(i-1):='U'; elsif s(i)='X' then sv(i-1):='X'; elsif s(i)='0' then sv(i-1):='0'; elsif s(i)='1' then sv(i-1):='1'; elsif s(i)='Z' then sv(i-1):='Z'; elsif s(i)='W' then sv(i-1):='W'; elsif s(i)='L' then sv(i-1):='L'; elsif s(i)='H' then sv(i-1):='H'; elsif s(i)='-' then sv(i-1):='-'; else report "Elemento desconocido" severity failure; end if; end loop; end procedure str2sv; procedure read(l: inout line; v: out std_logic_vector) is variable s : string(v'length downto 1); begin read(l,s); str2sv(s,v); end procedure read; -- Identificador para el archivo de datos file f : text; -- Señales auxiliares signal entrada : std_logic_vector(3 downto 0):="0000"; signal salida : std_logic_vector(3 downto 0); begin -- Instancia del dispositivo a probar DUT : BCD2Aiken port map(A => entrada, B => salida); -- Proceso secuencial que realiza las pruebas pruebas: process variable estimulo : std_logic_vector(3 downto 0); variable valor : std_logic_vector(3 downto 0); variable separa : character; variable l : line; variable status : file_open_status; begin report "Probando el conversor de BCD a Aiken" severity note; -- Abrimos el archivo file_open(status,f,"aiken_est.dat",read_mode); assert status=open_ok report "No se pudo abrir aiken_est.dat" severity failure; while not(endfile(f)) loop readline(f,l); -- Extraemos el estímulo read(l,estimulo); entrada <= estimulo; -- Extraemos el espacio separador read(l,separa); -- Extraemos el valor de referencia read(l,valor); wait for 1 ns; assert salida=valor report "Falla para "& integer'image(to_integer(unsigned(entrada))) severity failure; end loop; -- Cerramos el archivo file_close(f); report "Prueba exitosa!" severity note; wait; end process pruebas; end architecture Simulador; -- Entity: T_B2A_5
En ocasiones es posible que la interpretación de los resultados sea más compleja, y que no sea conveniente realizarla en VHDL. En otros casos puede que necesitemos realizar cálculos complicados con los resultados, por ejemplo estadísticas. En estos casos es posible utilizar un programa externo que lea los resultados y realice la verificación y/o cálculo. Un caso interesante es el de graficar los resultados de manera conveniente, por ejemplo graficar la imagen que obtendremos a la salida de un generador de video.
A modo ilustrativo usaremos el mismo conversor, los estímulos los generaremos en nuestro banco de pruebas y los resultados los guardaremos en un archivo aiken_res.dat. El test bench propuesto se encuentra en el Ejemplo 5-15.
Ejemplo 5-15. Volcado de los resultados (t_b2a_6.vhdl)
library IEEE; use IEEE.std_logic_1164.all; use IEEE.numeric_std.all; library std; use std.textio.all; entity T_B2A_6 is end entity T_B2A_6; architecture Simulador of T_B2A_6 is -- Declaración del componente a probar component BCD2Aiken is port( A : in std_logic_vector(3 downto 0); B : out std_logic_vector(3 downto 0)); end component BCD2Aiken; -- Funciones auxiliares procedure write(l: inout line; v: in std_logic_vector) is variable i : integer; variable s : string(3 downto 1); begin for i in v'range loop s:=std_logic'image(v(i)); write(l,s(2)); end loop; end procedure write; -- Identificador para el archivo de datos file f : text; -- Señales auxiliares signal entrada : std_logic_vector(3 downto 0):="0000"; signal salida : std_logic_vector(3 downto 0); begin -- Instancia del dispositivo a probar DUT : BCD2Aiken port map(A => entrada, B => salida); -- Proceso secuencial que realiza las pruebas pruebas: process variable valor : std_logic_vector(3 downto 0); variable l : line; variable status : file_open_status; begin report "Probando el conversor de BCD a Aiken" severity note; -- Abrimos el archivo file_open(status,f,"aiken_res.dat",write_mode); assert status=open_ok report "No se pudo crear aiken_res.dat" severity failure; for i in 0 to 15 loop entrada <= std_logic_vector(to_unsigned(i,4)); wait for 1 ns; -- No verificamos nada, nos limitamos a guardar el resultado write(l,salida); writeline(f,l); entrada <= std_logic_vector(unsigned(entrada)+1); end loop; -- Cerramos el archivo file_close(f); report "Fin de la prueba" severity note; wait; end process pruebas; end architecture Simulador; -- Entity: T_B2A_6
Un programa en lenguaje C capaz de verificar estos valores es el propuesto en el Ejemplo 5-16.
Ejemplo 5-16. Verificador de código Aiken (verif_aiken.c)
#include <stdio.h> #include <string.h> #include <stdlib.h> int LeerBinario(FILE *f) { char b[7]; if (fgets(b,7,f)) { int aux=0, mask=8, i; for (i=0; i<4; i++, mask>>=1) { if (b[i]=='1') aux+=mask; else if (b[i]!='0') { fprintf(stderr,"Metavalor encontrado '%c'\n",b[i]); exit(1); } } return aux; } return 0; } int Aiken(int i) { return i<5 ? i : i+6; } int main() { FILE *f; char buffer[7]; int i; puts("Probando el conversor de BCD a Aiken"); f=fopen("aiken_res.dat","rt"); for (i=0; i<10; i++) if (LeerBinario(f)!=Aiken(i)) { fprintf(stderr,"Error en el código %d\n",i); return 1; } for (; i<16; i++) if (strncmp(fgets(buffer,7,f),"XXXX",4)) { fprintf(stderr,"Error en el código %d (no da XXXX)\n",i); return 1; } puts("Prueba exitosa!"); return 0; }
En el caso anterior vimos como podíamos procesar los resultados externos, también vimos el caso en que los estímulos son generados externamente. En este ejemplo ilustraremos como hacer ambas cosas simultáneamente.
Un detalle importante es que el uso de archivos con nombres fijos, como hemos venido mostrando, tiene varias desventajas. Entre las principales desventajas podemos mencionar que obstruye el reuso de los programas que realizan los estímulos y/o verificaciones. Otro detalle es que si el volumen de datos es muy grande deberemos usar grandes cantidades de disco para almacenar los archivos. Para solucionar estos, y otros, problemas se recomienda hacer uso de la entrada y salida estándares, en lugar de archivos. Luego podremos decidir si los resultados irán a un archivo o serán directamente consumidos por otro proceso. Esto lo lograremos utilizando los mecanismos de redirección implementados por el shell de nuestro sistema.
En Windows el intérprete de línea de comandos, usualmente llamado shell, se llama cmd.exe. |
Continuando con nuestros ejemplos basados en el conversor de BCD natural a código Aiken nos proponemos generar los estímulos externamente, procesarlos en nuestro dispositivo bajo prueba y pasar los resultados para que sean verificados externamente.
Para generar nuestros estímulos se propone el código mostrado en el Ejemplo 5-17. Este programa simplemente genera todos los números binarios para una cantidad de bits especificados. Así, si queremos generar los 16 valores binarios de cuatro bits ejecutaremos gen_bin 4.
Ejemplo 5-17. Implementación en lenguaje C de un generador de valores binarios (gen_bin.c)
#include <stdio.h> #include <stdlib.h> void Binario(unsigned v, unsigned bits) { int mask=1<<(bits-1); for (; mask; mask>>=1) putc(v & mask ? '1' : '0',stdout); putc('\n',stdout); } int main(int argc, char *argv[]) { unsigned i, bits, max; bits=atoi(argv[1]); /* Cuantos bits */ max=1<<bits; for (i=0; i<max; i++) Binario(i,bits); return 0; }
Cuando se usa un shell, y queremos guardar la salida de un programa en un archivo, basta con usar la redirección de salida, esto es programa > archivo, por ejemplo: gen_bin > bin.dat. |
Para procesar los estímulos se puede utilizar un test bench como el mostrado en el Ejemplo 5-18. Notar que en este caso se utilizan la entrada estándar para recibir los datos y la salida estándar para reportar los resultados. Esto simplifica el código y lo hace más genérico.
Ejemplo 5-18. Lectura de estímulos y volcado de los resultados (t_b2a_7.vhdl)
library IEEE; use IEEE.std_logic_1164.all; use IEEE.numeric_std.all; library std; use std.textio.all; entity T_B2A_7 is end entity T_B2A_7; architecture Simulador of T_B2A_7 is -- Declaración del componente a probar component BCD2Aiken is port( A : in std_logic_vector(3 downto 0); B : out std_logic_vector(3 downto 0)); end component BCD2Aiken; -- Funciones auxiliares procedure str2sv(s: in string; sv: out std_logic_vector) is variable i : integer; begin for i in s'range loop if s(i)='U' then sv(i-1):='U'; elsif s(i)='X' then sv(i-1):='X'; elsif s(i)='0' then sv(i-1):='0'; elsif s(i)='1' then sv(i-1):='1'; elsif s(i)='Z' then sv(i-1):='Z'; elsif s(i)='W' then sv(i-1):='W'; elsif s(i)='L' then sv(i-1):='L'; elsif s(i)='H' then sv(i-1):='H'; elsif s(i)='-' then sv(i-1):='-'; else report "Elemento desconocido" severity failure; end if; end loop; end procedure str2sv; procedure read(l: inout line; v: out std_logic_vector) is variable s : string(v'length downto 1); begin read(l,s); str2sv(s,v); end procedure read; procedure write(l: inout line; v: in std_logic_vector) is variable i : integer; variable s : string(3 downto 1); begin for i in v'range loop s:=std_logic'image(v(i)); write(l,s(2)); end loop; end procedure write; -- Identificador para el archivo de datos file f : text; -- Señales auxiliares signal entrada : std_logic_vector(3 downto 0):="0000"; signal salida : std_logic_vector(3 downto 0); begin -- Instancia del dispositivo a probar DUT : BCD2Aiken port map(A => entrada, B => salida); -- Proceso secuencial que realiza las pruebas pruebas: process variable valor : std_logic_vector(3 downto 0); variable l : line; begin report "Probando el conversor de BCD a Aiken" severity note; while not(endfile(input)) loop -- Estímulo desde la entrada estándar readline(input,l); read(l,valor); entrada <= valor; wait for 1 ns; -- No verificamos nada, nos limitamos a guardar el resultado write(l,valor); write(l,' '); write(l,salida); writeline(output,l); end loop; report "Fin de la prueba" severity note; wait; end process pruebas; end architecture Simulador; -- Entity: T_B2A_7
La salida del test bench contiene el estímulo junto con el resultado. De esta manera el programa que verifica el resultado soporta que se prueben valores no contiguos. Una posible implementación que verifique los resultados es la que se muestra en el Ejemplo 5-19.
Ejemplo 5-19. Verificador de código Aiken, con estímulo (verif_aiken_est.c)
#include <stdio.h> #include <string.h> #include <stdlib.h> int LeerBinario(char *b) { int aux=0, mask=8, i; if (strncmp(b,"XXXX",4)==0) return -1; /* Usamos -1 para indicar XXXX */ for (i=0; i<4; i++, mask>>=1) { if (b[i]=='1') aux+=mask; else if (b[i]!='0') { fprintf(stderr,"Metavalor encontrado '%c'\n",b[i]); exit(1); } } return aux; } int Aiken(int i) { return i<10 ? (i<5 ? i : i+6) : -1; } int main() { char buffer[80]; int i, estimulo, resultado, referencia, casos=0; puts("Probando el conversor de BCD a Aiken"); while (fgets(buffer,80,stdin)) /* Leo una línea */ { estimulo=LeerBinario(buffer); resultado=LeerBinario(buffer+5); referencia=Aiken(estimulo); if (resultado!=referencia) { fprintf(stderr,"Error en el código %d, obtenido %d, esperado %d\n", estimulo,resultado,referencia); exit(1); } casos++; } printf("Prueba exitosa! (%d casos probados)\n",casos); return 0; }
Para ejecutar el conjunto bastará con usar el mecanismo de redirección de nuestro shell. En el caso de sistemas POSIX (Linux, UNIX, etc.) será ./gen_bin 4 | ./t_b2a_7 | ./verif_aiken_est.