0
votes

I'm trying to implement a VHDL-08 version of our PoC.Simulation helper package.

The original package uses shared variables to track the simulation status:

  • pass: all asserts passed
  • stop: stop all processes

There are several functions and procedures using that internal state. For example a tbGenerateClock() procedure is used to create a Clock signal.

In my VHDL-08 version, I'm using protected types for the shared variable. I implemented several 'Methods' (what is the correct terminus for that?), which are using or modifying the internal state variable.

GHDL compiles all my sources and throws an error at runtime: C:\Tools\GHDL\0.33dev\bin\ghdl.exe:internal error: protected_enter(2) C:\Tools\GHDL\0.33dev\bin\ghdl.exe:error: simulation failed

Is this a GHDL internal error or am I using protected types in a wrong way?

I created a (hopefully) minimal example (download from Gist) that has only 2 procedures: generateClock, stopSimulation.

There are also tb* wrapper procedures to ensure a compatible interface to my VHDL-93 implementation.

library IEEE;
use     IEEE.STD_LOGIC_1164.all;

package simulation is
  type tSimStatus is protected
    procedure stopSimulation;
    procedure generateClock(signal Clock : out STD_LOGIC; constant ClockPeriod : TIME; Enable : BOOLEAN := TRUE);
  end protected;
  -- stop all simulation processes
  procedure tbStopSimulation;
  -- Generate clock waveform for simulation
  procedure tbGenerateClock(signal Clock : out STD_LOGIC; constant ClockPeriod : TIME; Enable : BOOLEAN := TRUE);
end;

package body simulation is
  type tSimStatus is protected body
    variable stop : BOOLEAN := FALSE;

    procedure stopSimulation is
    begin
      stop := TRUE;
    end procedure;

    procedure generateClock(signal Clock : out STD_LOGIC; constant ClockPeriod : TIME; Enable : BOOLEAN := TRUE) is
      constant HIGH_TIME  : TIME  := ClockPeriod / 2;
      constant LOW_TIME    : TIME  := ClockPeriod / 2;
    begin
      Clock <= '1';
      while not stop loop
        while Enable and not stop loop
          Clock <= '1';
          wait for HIGH_TIME;
          Clock <= '0';
          wait for LOW_TIME;
        end loop;
        wait until (Enable = TRUE) or (stop = TRUE);
      end loop;
    end procedure;
  end protected body;

  shared variable status : tSimStatus;

  procedure tbStopSimulation is
  begin
    status.stopSimulation;
  end procedure;

  procedure tbGenerateClock(signal Clock : out STD_LOGIC; constant ClockPeriod : TIME; Enable : BOOLEAN := TRUE) is
  begin
    status.generateClock(Clock, ClockPeriod, Enable);
  end procedure;  
end package body;

library IEEE;
use      IEEE.STD_LOGIC_1164.all;
use      work.simulation.all;

entity tb is
end entity;

architecture test of tb is
  constant CLOCK_PERIOD : TIME := 10 ns;

  signal Clock    : STD_LOGIC;
  signal Reset    : STD_LOGIC    := '0';
begin
  -- clock process
  process
  begin
    tbGenerateClock(Clock, CLOCK_PERIOD, TRUE);
  end process;

  -- stimuli process
  process
  begin
    wait until rising_edge(Clock);

    Reset    <= '1';
    wait until rising_edge(Clock);

    Reset    <= '0';
    wait until rising_edge(Clock);

    tbStopSimulation;
    wait;
  end process;
end;

Solution:

  1. move clock generation code into the tbGenerateClock procedure
  2. add a function to the protected type to get the internal stop-state
  3. mark this function as impure

Here is the modified simulation package:

package simulation is
  type tSimStatus is protected
    procedure stopSimulation;
    impure function getState return BOOLEAN;
  end protected;
  -- stop all simulation processes
  procedure tbStopSimulation;
  -- Generate clock waveform for simulation
  procedure tbGenerateClock(signal Clock : out STD_LOGIC; constant ClockPeriod : TIME; Enable : BOOLEAN := TRUE);
end;

package body simulation is
  type tSimStatus is protected body
    variable stop : BOOLEAN := FALSE;

    procedure stopSimulation is
    begin
      stop := TRUE;
    end procedure;

    impure function getState return BOOLEAN is
    begin
      return stop;
    end function;
  end protected body;

  shared variable status : tSimStatus;

  procedure tbStopSimulation is
  begin
    status.stopSimulation;
  end procedure;

  procedure tbGenerateClock(signal Clock : out STD_LOGIC; constant ClockPeriod : TIME; Enable : BOOLEAN := TRUE) is
    constant HIGH_TIME  : TIME  := ClockPeriod / 2;
    constant LOW_TIME   : TIME  := ClockPeriod / 2;
  begin
    Clock <= '1';
    while not status.getState loop
      while Enable and not status.getState loop
        Clock <= '1';
        wait for HIGH_TIME;
        Clock <= '0';
        wait for LOW_TIME;
      end loop;
      wait until (Enable = TRUE) or (status.getState = TRUE);
    end loop;
  end procedure;  
end package body;
1
This question is not restricted to ghdl 0.33. It's just default ghdl :)Paebbels

1 Answers

2
votes

Protected types are supported in ghdl-0.31 for command line flag --std=02 (for the -2002 standard revision) and gives the same error.

There's a release candidate for 0.32, you're taking your life in own your hands for a 0.33dev which would represent some revision in the source tree beyond 0.32rc1.

Questions on ghdl internals are more properly addressed to the ghdl-discuss mail list and those capable of addressing them and participating on Stackoverflow don't do Windows.

I used a C shell alias

find . -iname \*.ad\[bs\] -exec grep -H !* \{\} \;

to find protected_enter in the ghdl source tree.

In ghdl's source grt/grt-processes.adb, procedure Ghdl_Protected_Enter1 we see comments -- Protected object already locked. and -- Should be locked by the current process. associated with the protected_enter(2) internal error.

You presumably have two separate processes seeking access to status.

These are the clock process and stimuli process (and you could have used labels).

IEEE Std 1076-2002, 12.5 Dynamic elaboration:

b) Execution of a subprogram call involves the elaboration of the parameter interface list of the corresponding subprogram declaration; this involves the elaboration of each interface declaration to create the corresponding formal parameters. Actual parameters are then associated with formal parameters. Next, if the subprogram is a method of a protected type (see 3.5.1) or an implicitly declared file operation (see 3.4.1), the elaboration blocks (suspends execution while retaining all state), if necessary, until exclusive access to the object denoted by the prefix of the method or to the file object denoted by the file parameter of the file operation is secured. ...

Method tbGenerateClock calls procedure status.generateClock which never returns unless Stop is TRUE, status is locked and Stop``TRUE depends on the stimuli process which is blocked.

The way out of this appears to be to add an argument to method tbGenerateClock and procedure generateClock eliminating method tbStopSimulation and procedure stopSimulation.

You're essentially operating status.generateClock as if it were a concurrent procedure call in a place only a sequential procedure call should be applicable. It loops while waiting on either input values or passage of time. The tbGenerateClock wrapper around the procedure call status.generateClock masks the issue (and there is no prohibition I can find in the standard).

It would seem the ghdl complaint is valid albeit the lack of discernible diagnostic messages is notable.

Further if you look at NOTES in 12.5:

2—If two or more processes access the same set of shared variables, livelock or deadlock may occur. That is, it may not be possible to ever grant exclusive access to the shared variable as outlined in item b), above. Implementations are allowed to, but not required to, detect and, if possible, resolve such conditions.

The resolution appears to have been to produce an unfathomable error message in lieu of simply allowing simulation time to reach TIME'HIGH, ending the simulation time after a board load of clock transitions.

Personally I'd imagine ghdl quite clever figuring out that there is a deadlock here.

I know of no documentation that can help you here other than reading the ghdl source noted above or developing a thorough understanding from the standard. (Along the lines of 'for the experienced programmer any errors are immediately obvious').

There is no guarantee that another VHDL implementation will indicate in any form that a deadlock has occurred, it's optional.

The behavior you might expect in that case would be a long simulation and eventually seeing a message to the effect that simulation was stopped by reaching TIME'HIGH, without the tbStopSimulation procedure call in the stimuli process ever having completed.

You'd essentially have even less information than you have with ghdl (again, 'for the experienced programmer any errors are immediately obvious').

And now you know protected_enter (and protected_leave) messages are associated with blocking access to protected methods by more than one process in ghdl. (And I had to find that out the hard way too.)

With paebbels solution

Moving the access to Stop outside the procedure generating Clock by using a protected method to access Stops value allows multiple processes to access shared data provided by the protected type, giving exclusive access to the shared variable status only long enough to complete the two methods (tbStopSimulation and impure function (method) getState).

It allows multiple processes to read or update Stop giving each exclusive access only long enough to complete the respective method.

I'd normally use a signal to share information between processes but in this case you still need the equivalent of a protected method to update Stop should you have multiple processes in an elaborated design able to stop simulation by stopping the clock.

There is also a potential issue of portability based on concurrency (a simulation cycle Stop is both read and updated).

You could have an accuracy of one clock because there's no guarantee of execution or resuming order for processes calling methods. If paebbels is using a pass/fail mechanism to evaluate the result of testing that won't be a problem, and that's a common solution.

So what testbench tb does with the change:

tb testbench with solution png (clickable)

is exactly what the you'll find in the unlabelled process with the comment

-- stimuli process