En ocasiones no es posible probar todos los valores de entrada posibles y se recurre a verificar sólo los valores extremos, junto con algunas muestras aleatorias intermedias. A esta estrategia se la suele denominar boundary testing. El mayor problema de este método es que si nuestro sistema es complejo el análisis y selección de que valores conviene probar puede ser sumamente complejo. La idea es probar los valores que sabemos que podrían generar problemas.
Para ilustrar esta metodología mostraremos dos casos. El primero referido a un circuito asincrónico y el segundo a otro sincrónico, en ambos casos se seleccionaron circuitos muy simples.
Como dispositivo a verificar hemos seleccionado un conversor de BCD natural a código Aiken. El código fuente del mismo puede encontrarse en el Ejemplo 5-1.
Ejemplo 5-1. Conversor de BCD natural a Aiken (bcd2aiken.vhdl)
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
entity BCD2Aiken is
port(
A : in std_logic_vector(3 downto 0);
B : out std_logic_vector(3 downto 0));
end entity BCD2Aiken;
architecture Flujo of BCD2Aiken is
begin
B <= A when unsigned(A)<5 else
std_logic_vector(unsigned(A)+6) when unsigned(A)<10 else
"XXXX";
end architecture Flujo; -- Entity: BCD2AikenComo valores a verificar hemos seleccionado: 0, 4, 5, 9 y 10, por tratarse de los extremos donde el código cambia. En el Ejemplo 5-2 vemos un banco de pruebas que realiza dicha verificación. Se puede observar que para esto se va asignando a la entrada los estímulos y luego se verifica que la salida corresponda a los valores esperados. Notar que es necesario esperar a que la entrada se propague a la salida, por tratarse de una simulación funcional, sin tener en cuenta los tiempos de un dispositivo en particular, se eligió un valor completamente arbitrario de tiempo para dicha espera.
Ejemplo 5-2. Prueba de extremos del conversor (t_b2a_1.vhdl)
library IEEE;
use IEEE.std_logic_1164.all;
entity T_B2A_1 is
end entity T_B2A_1;
architecture Simulador of T_B2A_1 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;
-- 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
begin
report "Probando el conversor de BCD a Aiken" severity note;
-- Primer valor de entrada
entrada <= "0000";
-- Esperamos a que se propague a la salida
wait for 1 ns;
-- Verificamos que el valor sea el esperado
assert salida="0000" report "Falla para 0000" severity failure;
-- Segundo valor
entrada <= "0100";
wait for 1 ns;
assert salida="0100" report "Falla para 0100" severity failure;
-- Los otros valores
entrada <= "0101";
wait for 1 ns;
assert salida="1011" report "Falla para 0101" severity failure;
entrada <= "1001";
wait for 1 ns;
assert salida="1111" report "Falla para 1001" severity failure;
entrada <= "1010";
wait for 1 ns;
assert salida="XXXX" report "Falla para 1010" severity failure;
-- Fin de las pruebas
report "Prueba exitosa!" severity note;
wait;
end process pruebas;
end architecture Simulador; -- Entity: T_B2A_1Este ejemplo es un tanto reiterativo, por lo que una forma de hacerlo más compacto es la que se muestra en el Ejemplo 5-3. En este caso los estímulos y los valores esperados se encuentran almacenados en vectores.
Ejemplo 5-3. Prueba de extremos del conversor, uso de vectores (t_b2a_2.vhdl)
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
entity T_B2A_2 is
end entity T_B2A_2;
architecture Simulador of T_B2A_2 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;
type vector is array (1 to 5) of std_logic_vector(3 downto 0);
-- Estímulos
constant ESTIMULO : vector:=("0000","0100","0101","1001","1010");
-- Valores esperados
constant REFERENCIA : vector:=("0000","0100","1011","1111","XXXX");
-- 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
begin
report "Probando el conversor de BCD a Aiken" severity note;
for i in 1 to 5 loop
entrada <= ESTIMULO(i);
wait for 1 ns;
assert salida=REFERENCIA(i) report "Falla para "&
integer'image(to_integer(unsigned(entrada)))
severity failure;
end loop;
report "Prueba exitosa!" severity note;
wait;
end process pruebas;
end architecture Simulador; -- Entity: T_B2A_2Como dispositivo a verificar hemos seleccionado un simple contador decimal, su implementación puede observarse en Ejemplo 5-4.
Ejemplo 5-4. Contador decimal (contador.vhdl)
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
entity Contador is
port(
clk : in std_logic;
reset : in std_logic;
valor : out unsigned(3 downto 0));
end entity Contador;
architecture RTL of Contador is
signal v : unsigned(3 downto 0);
begin
cuenta:
process (clk)
begin
if rising_edge(clk) then
if reset='1' or v=9 then
v <= (others => '0');
else
v <= v+1;
end if;
end if;
end process cuenta;
valor <= v;
end architecture RTL; -- Entity: ContadorPara su verificación hemos seleccionado comprobar las siguientes situaciones: estado inicial, una cuenta de 3, contar hasta 9 y desborde de 9 a 0. La implementación propuesta se puede observar en el Ejemplo 5-5.
Ejemplo 5-5. Verificación del contador decimal (t_contador.vhdl)
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
library std;
use std.textio.all;
entity T_Contador is
end entity T_Contador;
architecture Simulador of T_Contador is
component Contador is
port(
clk : in std_logic;
reset : in std_logic;
valor : out unsigned(3 downto 0));
end component Contador;
constant FREQ_CLK : integer:=50; -- Frecuencia del reloj en MHz
constant PERI_CLK : time:=1 us/FREQ_CLK;
signal clk : std_logic; -- Reloj
signal detener : boolean:=false; -- Termina el test
signal rst : std_logic; -- Pulso de reset
signal v : unsigned(3 downto 0); -- Valor del contador
begin
gen_reloj:
process
begin
clk <= '1', '0' after PERI_CLK/2;
wait for PERI_CLK;
if detener then
wait;
end if;
end process gen_reloj;
rst <= '1', '0' after PERI_CLK*3/2;
dut : Contador
port map(
clk => clk, reset => rst, valor => v);
do_test:
process
variable l : line;
begin
write(l,string'("* Comenzando el test"));
writeline(output,l);
wait until rst='0';
assert v=0 report "No arrancó en 0!" severity failure;
wait until rising_edge(clk); -- 1
wait until rising_edge(clk); -- 2
wait until rising_edge(clk); -- 3
wait for 1 fs; -- Esperar a que el tiempo pase!
assert v=3 report "No cuenta bien! ("&integer'image(to_integer(v))&")"
severity failure;
wait until rising_edge(clk); -- 4
wait until rising_edge(clk); -- 5
wait until rising_edge(clk); -- 6
wait until rising_edge(clk); -- 7
wait until rising_edge(clk); -- 8
wait until rising_edge(clk); -- 9
wait for 1 fs;
assert v=9 report "No cuenta bien! ("&integer'image(to_integer(v))&")"
severity failure;
wait until rising_edge(clk); -- 0 de nuevo
wait for 1 fs;
assert v=0 report "No vuelve a 0! ("&integer'image(to_integer(v))&")"
severity failure;
write(l,string'("* Todo OK!"));
writeline(output,l);
detener <= true;
wait;
end process do_test;
end architecture Simulador; -- Entity: T_ContadorEn este caso se optó por informar el progreso usando la salida estándar, este método genera mensajes más claros y evita confundir mensajes informativos con errores. Las demoras de 1 fs son a los simples fines de permitir que la señal de salida se actualice. Cabe mencionar que al haber elegido el tipo unsigned para la salida del contador se han podido evitar un número importante de conversiones de tipo, obteniéndose así un código mucho más simple y más entendible.