Xilinx ModelSim Simulation Tutorial

CSE 372 (Spring 2007): Digital Systems Organization and Design Lab

ModelSim is an application that integrates with Xilinx ISE to provide simulation and testing tools. Two kinds of simulation are used for testing a design: functional simulation and timing simulation. Functional simulation is used to make sure that the logic of a design is correct. Timing simulation also takes into account the timing properties of the logic and the FPGA, so you can see how long signals take to propagate and make sure that your design will behave as expected when it is downloaded onto the FPGA.

Functional Simulation

  1. Open the ISE Project Navigator and create a new project. After entering a project name and location, you'll be prompted for the project properties. Set the properties as shown below, making sure to select Modelsim as your Simulator.


    Click Next, and create a new Verilog Module source named full_adder. The inputs and outputs for the module are shown below. Once you've entered them, click Next and Finish until your module is generated.



    If you accidentally select a simulator other than Modelsim for your project, or if you open a previous project that had a different simulator selected, you can change the simulator by right-clicking on xc2vp30-7ff896 in the Sources window in ISE and selecting Properties.

  2. This tutorial will use a full adder that is the same as the one you created in Lab 0. You can use your own code, or copy the solution below:

    module full_adder(s, cout, a, b, cin);
        output s, cout;
        input a, b, cin;
        wire t1, t2, t3;
        xor (t1, a, b);
        xor (s, t1, cin);
        and (t2, t1, cin);
        and (t3, a, b);
        or  (cout, t2, t3);

    Run the Check Syntax process (under Synthesize) to make sure your code is entered correctly, and save your design.

  3. Right-click on full_adder.v in the Sources window and choose New Source. Select Verilog Test Fixture (not Verilog Module) and give your file a name such as "test_full_adder". Click Next, and you'll be prompted to associate the file with a module; choose full_adder, click Next, then click Finish. The file will be added to your project.

  4. ISE creates a skeleton test fixture (a.k.a. testbench) for you. The full_adder module is instantiated as the "unit under test" (uut). The inputs to the module are registers ("reg") because they are assigned in a procedural block (the section between "begin" and "end"). The outputs from the module are wires. The test simulation begins after the "initial begin" line. The values are initialized, and your code goes under the "Add stimulus here" line.

    The module is tested by assigning different values to the inputs in the test fixture, then running Modelsim to observe the outputs. Between assignments, delays are inserted using #n, where n is the number of nanoseconds of delay. For example, the following code tests when each of the inputs are high separately, changing the inputs every 25 ns:

    // Add stimulus here
    //125 ns
    #25;    a = 1'b1;
    //150 ns
    #25;    a = 1'b0;   b = 1'b1;
    //175 ns
    #25;    b = 1'b0;   cin = 1'b1;


    For the value 1'b0, the 1 represents the number of bits, the b stands for binary and the 0 represents the 1-bit binary value. Values can also be represented in decimal and hexadecimal; for example, 8'd27 represents 00011011 and 4'hA represents 1010.

    A good test fixture will test all or most of the possible inputs to the module, and especially any corner/boundary cases. Since the full adder only has three one-bit inputs, there are only eight possible input combinations, and your test fixture should simulate them all. Write the rest of the simulation code yourself; if you need help, you can view a completed test fixture here.

  5. After saving your test fixture, it will not immediately appear in the Sources window. To find the test fixture, click on the "Sources for:" drop-down box, and select Behavioral Simulation. The test fixture (test_full_adder.v) will now appear in the Sources hierarchy.


    To run the simulation in ModelSim, click on the test fixture in the Sources window to highlight it, expand the ModelSim Simulator option in the Processes window, and double-click Simulate Behavioral Model. ModelSim will open and run the test code in your test fixture file.


    Make sure your test fixture file, not the file containing your actual Verilog module, is selected in the Sources window before running ModelSim. Otherwise, the simulation will not run correctly.

  6. Once ModelSim starts, check the Transcript window carefully for warnings and errors.


    Warnings that say "Module 'module' does not have a `timescale directive in effect" can be ignored; other warnings and errors should be resolved before examining the simulation results.

    The black and green section of ModelSim is the waveform area. To scale the waveform correctly, move the horizontal slider all the way to the beginning (the left), then click the Zoom-Out 2x button until a proper scale is reached. For this design, a suitable scale is from 0 ps to about 200000 ps (200 ns) or 400000 ps (400 ns).


    The leftmost column of the wave area lists the input and output signals for the test module, test_full_adder_v. These signals can be rearranged and deleted. The column to the right lists the values of the signals at the cursor. To set the default cursor, Cursor 1, click anywhere in the waveform. To move the cursor to the exact location where a signal changes, click on the signal so it is highlighted in white, then click the Find Previous Transition and Find Next Transition buttons (TRANSITIONS) on the toolbar.


    Your output signals may have "false" transitions at times when more than one input signal changes. This is normal.


    "False" transitions in two output signals.

    The cursor can be locked by right-clicking on the time at the bottom of the cursor and choosing Lock Cursor 1. The cursor can be renamed by right-clicking on the white box in the lower-left. The time of the cursor can also be precisely set by right-clicking on the box to the right of the cursor name. You can create a new cursor by right-clicking at the bottom of the waveform and choosing New Cursor, or by using the Insert Cursor button on the toolbar. Cursors can be deleted in this way as well. If you have multiple cursors, the time between them is shown in white at the bottom of the waveform.


    A waveform with three cursors. Cursor 1 is locked, so it cannot be moved accidentally.

  7. Check that your module functions correctly by observing which of the input signals are high and checking that the outputs are correct. It may be helpful to drag the cursor across the waveform, so you can look at the numerical values of the signals instead of looking at the waves themselves. For modules with vector signals (rather than single-bit signals), you can right-click on the signal values and change the radix to display the values in binary, decimal, hexadecimal, etc. The output values have an "St" in front of them because they are set to the Symbolic radix by default; change this to Binary or another format.


    Use the Unsigned radix, rather than the Decimal radix, to view signals as decimal values. Signed decimal values can cause confusion.


    Drag the cursor across the waveform to see these values change, and check that the outputs are correct for the given inputs.

    Close ModelSim when you are finished. If you make changes to your module in ISE, the easiest way to restart the simulation is to close ModelSim, then run the Simulate Behavioral Model process again.


    The ModelSim license only allows one instance of ModelSim to run on a computer at any time.

Advanced Functional Simulation

  1. Instead of visually checking your simulation outputs every time you run the simulation, you can write test code that will change your inputs and check the outputs for you. Test code that does this is called a "task." Below is an example of a task for the full_adder module that takes five parameters: the three inputs for the module, and the two expected outputs. The task changes the inputs to the module, waits for the changes to propagate, then checks that the simulated outputs match the expected outputs. The task prints an error to the simulation console if the outputs do not match.

    task check_fa;
        input i_a;
        input i_b;
        input i_cin;
        input exp_s;
        input exp_cout;
            #25;    a = i_a;    b = i_b;    cin = i_cin;
            if (s !== exp_s) begin
                $display("Error at time=%dns s=%b, expected=%b", $time, s, exp_s);
                errors = errors + 1;
            if (cout !== exp_cout) begin
                $display("Error at time=%dns cout=%b, expected=%b", $time, cout, exp_cout);
                errors = errors + 1;

    The code for this task goes in your test fixture file, after the simulation block (from "initial begin" to "end"), but before the "endmodule." The task is "called" in the simulation block with the parameters you want to test your module with. For example, we can test all the possible inputs to the full_adder module with the following code:

    // Wait 100 ns for global reset to finish
    check_fa(0, 0, 0, 0, 0);
    check_fa(1, 0, 0, 1, 0);
    check_fa(0, 1, 0, 1, 0);
    check_fa(0, 0, 1, 1, 0);
    check_fa(1, 1, 0, 0, 1);
    check_fa(1, 0, 1, 0, 1);
    check_fa(0, 1, 1, 0, 1);
    check_fa(1, 1, 1, 1, 1);

    In addition to writing the task code and calling the task, we must also initialize the various variables. For a complete test fixture file that demonstrates the use of this task, click this link. If the simulation of your module produces any errors, the time of the error, the current output and the expected output will be displayed in blue in the ModelSim Transcript window, along with a count of the total number of errors. If your simulation has no errors, a message will be displayed telling you so.


    Simulation errors displayed in the ModelSim console.


    Don't just take the absence of error messages to mean that your module is working perfectly. An error in your simulation code may have prevented your tests from even being executed, or if there is an error in your module design and the value of a wire in your simulation is unknown (indicated by the value "x" and a red line in your waveforms), Modelsim may assume that any comparisons against this wire are true. Therefore, you should always check your waveforms in addition to the ModelSim console.


    A wire with an unknown value, most likely due to a module design error.


    If your simulation has errors, you can go back, fix your module and re-use the same test fixture to test the module again. It is a good idea to write your test fixture before you design your module (as long as you know the interface), because this will force you to think carefully about the expected outputs of the module and corner cases that should be tested. Once you have written a complete and accurate test fixture, it can continually be used to test your module.

  2. In some designs, you will want to observe internal signals that are not inputs to or outputs from the module. In the full adder, we can observe the intermediate internal wires t1, t2 and t3. Click on uut (unit under test) in the Workspace window (on the left side of the ModelSim window). All of the wires in the full_adder module appear in the Objects window. Drag t1, t2 and t3 to the signals section of the waveform to add them.



    Make sure you drag the signals from the Objects window. Dragging signals from the Workspace window will not work properly.

    Immediately after adding the signals, they will not have any waveforms, and will have a value of "No Data" if you move the cursor. In order to read these signals, ModelSim must run the simulation again. To do this, first click on the Restart button on the toolbar, leave all of the boxes checked on the subsequent prompt and click Restart. ModelSim will reload the simulation, but will not re-run it; click the Run -All button on the toolbar to start the simulation again. Waveforms will now appear for t1, t2 and t3.



    Restarting and running the simulation again will not incorporate any changes you have made to your module or test fixture. To see the effects of these changes, close ModelSim and run the Simulate Behavioral Model process again in ISE.

    There are other simulation controls that may be useful. The Break button stops a running simulation. The ContinueRun button continues a stopped simulation. The Run button runs the simulation for the amount of time entered in the box to the left of the button.



Designing and Testing an Adder

Verilog HDL On-Line Quick Reference

Authors: Peter Hornyack, Milo Martin