Search code examples
vhdlmodelsimtest-bench

How to go through multiple input combinations with a for loop in a testbench VHDL?


I'm new to VHDL and I'm writing a test bench for an XNOR gate. The simple solution was to manually go through each combination of the two inputs but with more inputs this will take too long. How can I write this as a for loop in VHDL?

process
begin
p0 <= '1';
p1 <= '0';
wait for 1 ns;
if (pout = '1') then
    error <= '1';
end if;
wait for 200 ns;
p0 <= '1';
p1 <= '1';
wait for 1 ns;
if (pout = '0') then
    error <= '1';
end if;
wait for 200 ns;
p0 <= '0';
p1 <= '1';
wait for 1 ns;
if (pout = '1') then
    error <= '1';
end if;
wait for 200 ns;
p0 <= '0';
p1 <= '0';
wait for 1 ns;
if (pout = '0') then
    error <= '1';
end if;
wait for 200 ns;
end process;

Solution

  • If p0 and p1 are inputs to the device under test and their base type is compatible with the element type of type unsigned:

    library ieee;
    use ieee.std_logic_1164.all;
    
    entity xnor2 is
        port (
            p0:     in  std_logic;
            p1:     in  std_logic;
            pout:   out std_logic
        );
    end entity;
    
    architecture foo of xnor2 is
    begin
        pout <= not (p0 xor p1);
    end architecture;
    
    library ieee;
    use ieee.std_logic_1164.all;
    
    entity inputs is
    end entity;
    
    architecture foo of inputs is
        signal p0, p1, pout:    std_logic;
        signal error:           std_logic := '0';
    begin
    DUT:
        entity work.xnor2
            port map (
                p0 => p0,
                p1 => p1,
                pout => pout
            );
    
        process
            use ieee.numeric_std.all;  -- for example, if not already visible
            variable elements: unsigned (1 downto 0);
        begin
            elements := (others => '0');
            for i in 0 to 2 ** elements'length  - 1 loop
                
                p0 <= elements(0);
                p1 <= elements(1);
                wait for 1 ns;
                report LF & "i = " & integer'image(i) &
                    LF & HT & "p0 = " & std_ulogic'image(p0) & 
                             " p1 = " & std_ulogic'image(p1) &
                             " error = " & std_ulogic'image(error);
                if pout = '0' then
                    error <= '1';
                end if;
                wait for 200 ns;
                elements := elements + 1;
            end loop;
            wait;
        end process;
    end architecture;
    

    Which reports:

    ghdl -r inputs
    inputs.vhdl:45:13:@1ns:(report note):
    i = 0
        p0 = '0' p1 = '0' error = '0'
    inputs.vhdl:45:13:@202ns:(report note):
    i = 1
        p0 = '1' p1 = '0' error = '0'
    inputs.vhdl:45:13:@403ns:(report note):
    i = 2
        p0 = '0' p1 = '1' error = '1'
    inputs.vhdl:45:13:@604ns:(report note):
    i = 3
        p0 = '1' p1 = '1' error = '1'
    

    Where we also see error has no obvious meaning.

    Without providing a minimal, complete, and verifiable example in the question there's some risk an answer may have one or more errors and future readers can't as easily verify the solution.

    The idea here is to use a binary representing counter with as many bits (elements) as inputs and assign the value of bits (elements) to respective inputs in each loop iteration.

    That binary value can also be provided directly from the integer value of the loop parameter. See How to easily group and drive signals in VHDL testbench which also uses aggregate assignment:

    library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    
    entity agg_assign is
    end entity;
    
    architecture foo of agg_assign is
        signal A, B, C: std_logic;
    begin
        process
        begin
            wait for 10 ns;
            for i in 0 to 7 loop
                (A, B, C) <= std_logic_vector(to_unsigned(i, 3));
                wait for 10 ns;
            end loop;
            wait;
        end process;
    end architecture;
    

    You can also create a record subtype to mix element and array assignments:

    library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    
    entity aggr_rec_assign is
    end entity;
    
    architecture foo of aggr_rec_assign is
        signal A, B, C: std_logic;
        signal D:       std_logic_vector (2 downto 0);
        
        function to_string (inp: std_logic_vector) return string is
            variable image_str: string (1 to inp'length);
            alias input_str:  std_logic_vector (1 to inp'length) is inp;
        begin
            for i in input_str'range loop
                image_str(i) := character'VALUE(std_ulogic'IMAGE(input_str(i)));
            end loop;
            return image_str;
        end function;
    begin
        process
            type inputs_rec is
            record
                    A:  std_logic;
                    B:  std_logic;
                    C:  std_logic;
                    D:  std_logic_vector (2 downto 0);
                end record;
                variable elements:  unsigned (5 downto 0);
        begin
            wait for 10 ns;
            for i in 0 to 2 ** elements'length - 1 loop
                elements := to_unsigned(i, elements'length);
                (A, B, C, D) <= 
                    inputs_rec'(
                        elements(5),
                        elements(4),
                        elements(3),
                        std_logic_vector(elements(2 downto 0))
                    );
                wait for 10 ns;
                report LF & HT & "i =  "& integer'image(i) & " (A, B, C, D) = " & 
                    std_ulogic'image(A) & " " &
                    std_ulogic'image(B) & " " &
                    std_ulogic'image(C) & " " &
                    to_string(D);
            end loop;
            wait;
        end process;
    end architecture;
    

    where in both cases the aggregate assignment would be the place to select the order inputs are extracted from the binary value.