This website uses cookies. By using this site, you consent to the use of cookies. For more information, please take a look at our Privacy Policy.
Home > FPGA Technical Tutorials > Design Recipes for FPGAs Using Verilog and VHDL > Design Automation of FPGAs > Simulation

TABLE OF CONTENTS

Xilinx FPGA FPGA Forum

Simulation

FONT SIZE : AAA

Simulators

Simulators are a key aspect of the design of FPGAs. They are how we understand the behavior of our designs, check the results are correct, identify syntax errors and even check postsynthesis functionality to ensure that the designs will operate as designed when deployed in a real device. There are a number of simulators available (all the main design automation vendors provide software) and the FPGA vendors also will supply a simulator usually wrapped up with the design flow software for their own devices. This altruistic approach is very useful in learning to use FPGAs as, while they are clearly going to be targeted with libraries at the vendor’s devices, the basic simulators tend to be fully featured enough to be very useful for most circumstances. One of the most prevalent simulators is the ModelSim ® from Mentor Graphics, which is generally wrapped in with the Altera and Xilinx design software kits and is very commonly used in universities for teaching purposes. It is a general-purpose simulator which can cater for VHDL, Verilog, SystemVerilog and a mixture of all these languages in a general simulation framework. A screen shot of ModelSim in use is shown in Figure 5.1 and it shows the compilation window and a waveform viewer. There are other ways to use the tool including a transcript window and variable lists rather than waveforms—useful in seeing transitions or events. 

Test Benches

The overall goal of any hardware design is to ensure that the design meets the requirements of the design specification. In order to measure and determine whether this is indeed the case, we need not only to simulate the design representation in a hardware description language (such as VHDL or Verilog), but also to ensure that whatever tests we undertake are appropriate and demonstrate that the specification has been met. 

The way that designers can test their designs in a simulator is by creating a test bench. This is directly analogous to a real experimental test bench in the sense that stimuli are defined and the responses of the circuit measured to ensure that they meet the specification. 

In practice, the test bench is simply a model that generates the required stimuli and checks the responses. This can be in such a way that the designer can view the waveforms and manually check them, or by using appropriate HDL constructs to check the design responses automatically. 

Test Bench Goals

The goals of any test bench are twofold. The first is primarily to ensure that correct operation is achieved. This is essentially a functional test. The second goal is to ensure that a synthesized design still meets the specification (particularly with a view to timing errors).

Simple Test Bench: Instantiating Components

Consider a simple model of a VHDL counter given as follows:

1 library ieee;

2 use ieee.std_logic_1164.all;

3 use ieee.numeric_std.all;

4

5 entity counter is

6 generic (

7 n : integer := 4

8 );

9 port (

10 clk : in std_logic;

11 rst : in std_logic;

12 output : out std_logic_vector((n−1) downto 0)

13 );

14 end;

15

16 architecture simple of counter is

17 begin

18 process(clk, rst)

19 variable count : unsigned((n−1) downto 0);

20 begin

21 if rst = ’0’ then

22 count := (others => ’0’);

23 elsif rising_edge(clk) then

24 count := count + 1;

25 end if;

26 output <= std_logic_vector(count);

27 end process;

28 end;

This simple model is a simple counter with a clock and reset signal, and to test the operation of the component we need to do several things. 

First, we must include the component in a new design. So we need to create a basic test bench. The listing below shows how a basic entity (with no connections) is created, and then the architecture contains both the component declaration and the signals to test the design. 

1 library ieee;

2 use ieee.std_logic_1164.all;

3 use ieee.numeric_std.all;

4

5 entity CounterTest is

6 end CounterTest;

7

8 architecture stimulus of CounterTest is

9 signal rst : std_logic := ’0’;

10 signal clk : std_logic:=’0’;

11 signal count : std_logic_vector (3 downto 0);

12

13 component counter

14 port(

15 clk : in std_logic;

16 rst : in std_logic;

17 output : out std_logic_vector(3 downto 0)

18 );

19 end component;

20 for all : counter use entity work.counter ;

21

22 begin

23 DUT: counter port map(clk=>clk,rst=>rst,output=>count);

24 clk <= not clk after 1 us;

25 process

26 begin

27 rst<=’0’,’1’ after 2.5 us;

28 wait;

29 end process;

30 end;

This test bench will compile in a simulator, and needs to have definitions of the input stimuli (clk and rst) that will exercise the circuit under test (CUT). 

If we wish to add stimuli to our test bench we have some significant advantages over our model; the most appealing is that we generally do not need to adhere to any design rules or even make the code synthesizable. Test bench code is generally designed to be off chip and therefore we can make the code as abstract or behavioral as we like and it will still be fit for purpose. We can use wait statements, file read and write, assertions and other nonsynthesizable code options. 

Adding Stimuli

In order to add a basic set of stimuli to our test bench, we could simply define the values of the input signals clk and rst with a simple signal assignment:

1 clk <= ’1’;

2 rst <= ’0’;

Clearly this is not a very complex or dynamic test bench, so to add a sequence of events we can modify the signal assignments to include numerous value, time pairs defining a sequence of values.

In our simple example, we wish to go from a reset state to actively counting and so after the rst signal has been initialized to 0, and then set to 1, the clk signal will then increment the counter. In our example we have used a process to set up the clock repetition and a separate process to initialize and then remove the reset. 

1 clk <= not clk after 1 us;

2 process

3 begin

4 rst<=’0’,’1’ after 2.5 us;

5 wait;

6 end process;

Figure 5.2

ModelSim simulator waveform results.

While this method is useful for small circuits, clearly for more complex realistic designs it is of limited value. Another approach is to define a constant array of values that allow a number of tests to be carried out with a relatively simple test bench and applying a different set of stimuli and responses in turn. The resulting behavior can then be analyzed looking at the waveforms with measurements and markers to ensure the correct timing and functionality, as is shown in Figure 5.2. 

For example, we can exhaustively test our simple two-input logic design using a set of data in a record. A record is simply a collection of types grouped together defined as a new type. With a new composite type, such as a record, we can then create an array, just as in any standard type. This requires another type declaration, of the array type itself. With these two new types we can simply declare a constant (of type data_array) that is an array of record values (of type testdata) that fully describe the data set to be used to test the design. Notice that the type data_array does not have a default range, but that this is defined by the declaration in this particular test bench. The beauty of this approach is that we can change from a system that requires every test stimulus to be defined explicitly, to one where a generic test data process will read values from predefined arrays of data. In the simple test example presented here, an example process to apply each set of test data in turn could be implemented as follows: 

1 process

2 begin

3 for i in test_data’range loop

4 in0 <= test_data(i).in0;

5 in1 <= test_data(i).in1;

6 wait for 100 ns;

7 end loop

8 wait;

9 end process;

There are several interesting aspects to this piece of test bench VHDL. The first is that we can use behavioral VHDL (wait for 100ns) as we are not constrained to synthesize this to hardware. Secondly, by using the range operator, the test bench becomes unconstrained by the size of the data set. Finally, the individual record elements are accessed using the hierarchical construct test_data(i).in0 or test_data(i).in1 respectively. 

Assertions

Assertions are a method of mechanically testing values of variables under strict conditions and are prevalent in automated tests of circuits. This is a method by which the timing and values of variables (i.e., signals) can be evaluated against test criteria in a test bench and thus the process of model validation can be automated. This is particularly useful for large designs and also to ensure that any changes do not violate the overall design. It is important to note that assertions are not used in many senses to debug a model, but rather to check it is correct in a more formal sense. 

For example, we can set an internal variable (flag) to highlight a certain condition (for example, that two outputs have been set to ‘1’), and then use this with an assert statement to report this to the designer and when it has occurred. For example, consider this simple VHDL code: 

1 assert flag = (x1 and x2)

2 report ”The condition has been violated: circuit error”

3 severity Error;

If the condition occurs, then during a simulation the assertion will take place and the message reported in the transcript:

1 # ∗∗ The condition has been violated: circuit error

2 # Time: 100 ns Iteration: 0 Instance: /testbench

This is very useful from a verification perspective as the user can see exactly when the error occurred, but it is a bit cumbersome for debug purposes. The severity of the assertion can be one of note, warning, error, and failure. 

The same basic mechanism operates in Verilog, with the same keyword assert being used. If we consider the same example, the Verilog code would be something like this: 

1 assert (x1 & x2) $error( ”The condition has been violated: circuit error”)

In Verilog, the severity command resulting from the assertion can be $fatal, $error (which is the default severity) and $warning.

  • XC3S250E-4VQ100C

    Manufacturer:Xilinx

  • FPGA Spartan-3E Family 250K Gates 5508 Cells 572MHz 90nm Technology 1.2V 100-Pin VTQFP
  • Product Categories: FPGAs

    Lifecycle:Active Active

    RoHS: No RoHS

  • XC3S250E-5PQ208C

    Manufacturer:Xilinx

  • FPGA Spartan-3E Family 250K Gates 5508 Cells 657MHz 90nm Technology 1.2V 208-Pin PQFP
  • Product Categories: FPGAs

    Lifecycle:Active Active

    RoHS: No RoHS

  • XC2018-100PC84C

    Manufacturer:Xilinx

  • Field Programmable Gate Array (FPGA)
  • Product Categories:

    Lifecycle:Any -

    RoHS: -

  • XC4020E-3HQ208I

    Manufacturer:Xilinx

  • FPGA XC4000E Family 20K Gates 1862 Cells 0.35um Technology 5V 208-Pin HSPQFP EP
  • Product Categories: FPGAs (Field Programmable Gate Array)

    Lifecycle:Obsolete -

    RoHS: No RoHS

  • XC4VSX25-10FF668C

    Manufacturer:Xilinx

  • FPGA Virtex-4 SX Family 23040 Cells 90nm Technology 1.2V 668-Pin FCBGA
  • Product Categories: FPGAs

    Lifecycle:Active Active

    RoHS: No RoHS

Need Help?

Support

If you have any questions about the product and related issues, Please contact us.