1

I have the following simple FSM description in VHDL:

library ieee;
use ieee.std_logic_1164.all;

entity coverage1 is
  port (
    clk : in  std_logic;
    rst : in  std_logic;
    req : in  std_logic;
    ack : out std_logic
  );
end entity coverage1;

architecture rtl of coverage1 is
  type STATES is (IDLE, RUNNING, FINISH);
  signal fsm_cs : STATES := IDLE;
  signal fsm_ns : STATES;
begin

  process (fsm_cs, req) is
  begin
    fsm_ns <= fsm_cs;
    ack    <= '0';

    case fsm_cs is
      when IDLE =>
        if req = '1' then
          fsm_ns <= RUNNING;
        end if;

      when RUNNING =>
        fsm_ns <= FINISH;

      when FINISH =>
        ack <= '1';
        fsm_ns <= IDLE;

      when others =>
              null;
    end case;
  end process;

  process (clk) is
  begin
    if rising_edge(clk) then
      if rst = '1' then
        fsm_cs <= IDLE;
      else
        fsm_cs <= fsm_ns;
      end if;
    end if;
  end process;
end architecture;

And this testbench:

library ieee;
use ieee.std_logic_1164.all;

entity coverage1_tb is
end entity coverage1_tb;

architecture tb of coverage1_tb is
  signal clk : std_logic := '1';
  signal rst : std_logic;
  signal req : std_logic;
  signal ack : std_logic;

  signal finished : boolean := false;
begin
  coverage1_1: entity work.coverage1
    port map (
      clk => clk,
      rst => rst,
      req => req,
      rdy => rdy,
      ack => ack);

  clk <= not clk after 5 ns when not finished else unaffected;

  process
  begin
    rst <= '1';
    wait until rising_edge(clk);
    rst <= '0';
    req <= '0';
    wait until rising_edge(clk);
    req <= '1';
    wait until rising_edge(clk);
    req <= '0';
    wait until rising_edge(clk) and ack = '1';
    wait until rising_edge(clk);
    finished <= true;
    wait;
  end process;
end architecture tb;

The FSM does not reach 100% code coverage in ModelSim/QuestaSim. I found two issues:

  1. The others case, which is not required because the enumeration is fully covered by all choices, is requested to be covered. But this branch is not reachable... Why does QuestaSim expect coverage for this branch?

  2. QuestaSim shows a false state diagram for my example FSM. The diagram contains self-edges for the states: RUNNING and FINISH. These edges do not exist nor can they be covered.
    If I remove the default assignment fsm_ns <= fsm_cs; and add an else branch in the IDLE state, I'll get full coverage.

    if req = '1' then
      fsm_ns <= RUNNING;
    else
      fsm_ns <= IDLE;
    end if;
    

    Why does the state diagram show false edges and why can't I use default assignments?

I could live with bullet item 1, but item 2 is a problem. If I write my FSMs in that style, I'm duplicating a lot of unneeded code and most synthesizers won't recognize the FSM pattern! So I'll lose FSM optimizations and checking in synthesis.

Paebbels
  • 15,573
  • 13
  • 70
  • 139
  • What if you remove the `others` case? Then QuestaSim can't request it to be covered. Or do you mean that it complains if there is no `others`, even though all cases are handled explitly? – mkrieger1 Feb 06 '17 at 23:39
  • I can remove the others case, but if quests fails on such a simple example what happens in more complex scenarios? – Paebbels Feb 07 '17 at 03:24
  • 10.9 Case statement tells us the others choice is legal here. Determining choices against type STATES is outside the domain of an code execution profiling tool while not outside the domain of VHDL analysis of a case statement. tmeissner's -- coverage off/on appears to be a valid solution. You're missing a declaration for the actual of port rdy in the testbench as Brian alludes. –  Apr 16 '18 at 02:03

2 Answers2

2

Some observations, again using ghdl.

Commenting out the rdy port, ghdl again reports 100% coverage.

Ironically the null in "others" clause gets 20 hits ... which is suspicious. As it's the last active line in the process I believe any events that wake the process but don't do anything are recorded here.

A null; added after end case collects these 20 hits, confirming this - but the others case still isn't recorded as a coverage hole (despite having no hits). My hypothesis is that because null generates no code it isn't being tracked. Adding a harmless but trackable action fsm_ns <= IDLE; to the when others branch now gives a coverage hole (which, annoyingly, receives spurious hits when the null after end case is removed.

Summary :

  • it's worth testing the effect of an active statement as a hook for tracking coverage in when others, and a null after end case so that "end of process" code isn't incorrectly logged on the last case arm
  • ghdl needs some tidying up in these two areas, perhaps translating null as a 'nop' to hook the coverage to.

enter image description here

Sorry I can't shed light on Modelsim's behaviour here.

However, code that is present but not reachable - "dead code" - is seen as representing a design error in high integrity practices, so I regard Modelsim as correct to highlight it, and ghdl as incorrect in cases where it does not.

It's somewhat related to the issue of safe state machine design where an SEU (perhaps from a cosmic ray) corrupts the state register. Note that with less than 2**n members of STATES, there WILL be an "other" state, and with a null action, this SM will lock up there if it ever reaches that state. (However, deleting the "others" clause won't correct that, and a synthesis tool may conclude the "others" clause is unreachable and delete it anyway. Safe SM design is another topic)

1
  1. The when others is shown as not covered, as expected. You can exclude it with:

    -- coverage off
    when others => null;
    -- coverage on
    

    I do this in every case statement where the others case can't be hit.

  2. I get 100% state coverage, even without the else branch. The if conditional in IDLE state has 100% branch coverage, even without an else branch (Active: 4, True Hits: 1, AllFalse: 3). For 100% FSM coverage you should exclude the implicit changes by the reset signal, or you have to pull the reset in every FSM state. You can exclude the reset state changes with -nofsmresettrans swith when compiling.

I get the same behaviour using Modelsim DE 10.5c and 10.6 and Questa 10.6.

BTW: I can't get FSM coverage if parts of the FSM are inside a generate block which depends on a generic, so I had to outcomment the generate stuff and only leave in one of the reset processes. I think this is an Modelsim/Questa limitation that it don't recognizes FSMs inside of generate blocks, but I'm not. The help also hints that FSMs using generics aren't regognized. Maybe that's the case here.

tmeissner
  • 21
  • 4