VHDL State Machine testbench

14.2k views Asked by At

Description:

I am trying to generate a test bench for a 5 state sequential state machine that detects 110 or any combination of (2) 1's and (1) 0. I already have written the code. see below. I am having trouble with the test bench which is wrong. I want to test for all possible sequences as well as input combinations that are off sequence.

Please give me examples of a good test bench to achieve what I need for a mealy machine.

vhdl code:

library IEEE;
use IEEE.STD_LOGIC_1164.all;

entity state is
port( clk, x : in std_logic;
      z : out std_logic
      );
end entity;

architecture behavioral of state is
type state_type is (s0,s1,s2,s3,s4);
signal state,next_s: state_type;

------------------------------------------------------------------------------

begin
process (state,x)
begin
if clk='1' and clk'event then

case state is
 when s0 =>
   if(x ='0') then
     z <= '0';
     next_s <= s4;
   else
     z <= '0';
     next_s <= s1;
   end if;

 when s1 => --when current state is "s1"
   if(x ='0') then
     z <= '0';
     next_s <= s3;
   else
     z <= '0';
     next_s <= s2;
   end if;

 when s2 =>  --when current state is "s2"
   if(x ='0') then
     z <= '1';
     next_s <= s0;
   else
     z <= '0';
     next_s <= s0;
   end if;

 when s3 =>  --when current state is "s3"
   if(x ='0') then
     z <= '0';
     next_s <= s0;
   else
     z <= '1';
     next_s <= s0;
   end if;

 when s4 => --when current state is s4
   if (x = '0') then
     z <= '0';
     next_s <= s0;
   else
     z <= '0';
     next_s <= s3;
   end if;
end case;
end if;

end process;
end behavioral;

Test Bench code:

library ieee;
use ieee.std_logic_1164.all;

-- Add your library and packages declaration here ...

entity state_tb is
end state_tb;

architecture TB_ARCHITECTURE of state_tb is
-- Component declaration of the tested unit
component state
port(
    clk : in STD_LOGIC;
    x : in STD_LOGIC;
    z : out STD_LOGIC );
end component;

-- Stimulus signals - signals mapped to the input and inout ports of tested entity
signal clk : STD_LOGIC;
signal x : STD_LOGIC;
-- Observed signals - signals mapped to the output ports of tested entity
signal z : STD_LOGIC;

-- Add your code here ...

begin

-- Unit Under Test port map
UUT : state
    port map (
        clk => clk,
        x => x,
        z => z
    );

-- CLOCK STIMULI
CLOCK: process
begin
CLK <= not clk after 20 ns;
wait for 40 ns;
end process; 

-- X input STIMULI
X_Stimuli: process
begin
X <= not x after 40 ns;
wait for 80 ns;
end process;

end TB_ARCHITECTURE;

configuration TESTBENCH_FOR_state of state_tb is
for TB_ARCHITECTURE
    for UUT : state
        use entity work.state(behavioral);
    end for;
end for;
end TESTBENCH_FOR_state;
2

There are 2 answers

0
rick On

These are some problems with both the FSM code and the testbench code in your example, but the main issue is that to test an FSM you need t apply a sequence of input values and check the outputs. You can't just toggle your input signal between 1 and 0. So, here's some advice:

  • First, you have to decide whether you want a generic FSM that detects any input sequence, or a FSM that detects only a single sequence (your code shows the second option)
  • You need to consider the time dimension in your test. Yours is a clocked circuit, meaning that each test will take several clock cycles.
  • To test every possible input sequence, I suggest that you create a procedure that takes as arguments:
    • A sequence of 4 input values to the FSM (could be a std_logic_vector)
    • A sequence of 4 output values that you expect to see
    • (optionally) a sequence of the 4 states you expect the FSM will go through

Your procedure could look like:

    procedure test_sequence(
        input_sequence: std_logic_vector;
        expected_output_sequence: std_logic_vector
    ) is begin
        for i in input_sequence'range loop
            x <= input_sequence(i);
            wait until rising_edge(clk);
            assert z = expected_output_sequence(i);
        end loop;
    end;

Then, in your main tests process, you can test one sequence with:

test_sequence(
    input_sequence => "110",
    expected_output_sequence => "001"
);

Some other suggestions:

  • You should add a reset signal to make testing easier and to prevent mismatchs between simulation and synthesis
  • There is no need for a configuration in your case, you can remove it from your code
  • Your FSM code is incomplete because you never update your current state
  • In a testbench like the one you are using, you need to initialize the signals used as input to the DUT (x and clk)

Note that the procedure above needs to be inside a process' declarative region. Something like:

main_test_process: process is

    procedure test_sequence(
        input_sequence: std_logic_vector;
        expected_output_sequence: std_logic_vector
    ) is begin
        for i in input_sequence'range loop
            x <= input_sequence(i);
            wait until rising_edge(clk);
            assert z = expected_output_sequence(i);
        end loop;
    end;

begin

    test_sequence( input_sequence => "000", expected_output_sequence => "000");
    test_sequence( input_sequence => "001", expected_output_sequence => "000");
    --  (add any other input sequences here...)
    test_sequence( input_sequence => "110", expected_output_sequence => "001");

    std.env.finish;

end process;

should work.

0
mbschenkel On

Your statemachine has the following possible cycles with 2 or 3 steps before going back to s0 and correctly detects sequencies of two 1s.

Case (x1,x2,x3) States  (z1,z2,z3)
  0    0,0,0     4,0,... 0,0,...   (starts again at s0)             
  1    0,0,1     4,0,... 0,0,...   (starts again at s0)             
  2    0,1,0     4,3,0   0,0,0     (covered by your TB)         
  3    0,1,1     4,3,0   0,0,1              
  4    1,0,0     1,3,0   0,0,0              
  5    1,0,1     1,3,0   0,0,1     (covered by your TB)         
  6    1,1,0     1,2,0   0,0,1              
  7    1,1,1     1,2,0   0,0,0              

As I see it your creating you're stimuli as follows

          __    __    __    __    __
clk    __|  |__|  |__|  |__|  |__|  |__...
             _____       _____       _____ 
x      _____|     |_____|     |_____|     |...

I.e. because in each section with x=1 you have exactly one rising clock and therefore you're only testing sequencies with a 0101010... pattern, where your statemachine would go on one of the two paths marked in the table above. This means that the other 6 possible pathes are never executed in your testbench.

Since this statemachine has a small and finite number of paths I would recommend an exhaustive test where you would essentially cycle through the 8 possible cases listed above; this could be easily implemented with a 3bit counter. So you would create a sequence in the form

reset
test-case 0 (sequence 0,0,0)
reset
test-case 1 (and so on)

That would require you to add a reset to the entity state. Alternatively you could modify your statemachine to remain in s0 with a zero-input; then you could reset with a sequence of 0,0,0 at any time.