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: BCD2Aiken
Como 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_1
Este 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_2
Como 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: Contador
Para 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_Contador
En 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.