5.3. Uso de archivos

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.

5.3.1. Referencia desde un archivo

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.

5.3.2. Estímulo y referencia desde un archivo

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

5.3.3. Verificación externa

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;
}

5.3.4. Estímulos y verificación separados

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.

Sugerencia

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;
}

Sugerencia

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.

Copyright © 2011 UTN FRBA - INTI - Ing. Salvador E. Tropea