Tutorial 2: Simulation

In the last tutorial, we programmed our device and tested it by physically switching all the switches. That is all the testing you’re going to need for the simplest FPGA in the world. In reality, things can be somewhat more complicated. It becomes difficult to test your FPGA by manipulating it physically. This is why we use simulation. By simulating your design, you can check to see if it works, and then spend very little time debugging it in the lab. In this tutorial we will simulate our design. This will be the simplest simulation in the world.

The Test Bench

You can’t just simulate your design in isolation. Your design has input signals and output signals, and you need to control the inputs and verify that the outputs are correct. Ideally you want your test to create the input stimulus and verify the correctness of the output signals. In the real world, we want tests to print out what they are doing as the test runs, print any error messages if any of the tests fail, and print a summary about whether the test passed or failed.

In our example, we will only have one file in our test bench. In the real world you will usually have many files that form the test bench and the tests. In fact, in complicated ASIC designs which require more elaborate testing than FPGAs, it is not uncommon for more effort to be put into the simulation and testing of the ASIC design than in the design itself.

To create the test bench go to the Project Manager flow and click the Add Sources button. Check the Add or create simulation sources radio button and click Next. Then click Create File… and give a file name called bench.v and click Finish. A new dialog will pop up and you should name the module bench. Don’t give it any ports. Then click OK.

 Test Bench Contents

Unlike our FPGA, the test bench doesn’t need any input or output ports. You can think of it as the container for the simulation universe. Everything happens within the test bench. Another thing you need to know about is the simulation timescale. At the very top of the test bench file you will see (or you should type)

`timescale 1ns/1ns

This tells the simulator that, when you give a delay value in this file, the delay will be in nanoseconds. That is the numerator of the fraction value. The denominator of the fraction value tells the simulator how much time precision this file requires. In general, it is OK to leave it the same as the numerator. Note that the timescale directive, like all preprocessor directives in verilog, starts with a back tick (grave accent) character. Next comes the module declaration.

module bench;

Following that we need to declare the signals that we are going to connect to our FPGA that we will be simulating. The output signals from the FPGA need to be connected to wire type signals in the test bench. The input signals to the FPGA can come from wire or reg signals. The first thing to declare is the clock input:

reg clk = 1;

And then, we create a 100MHz clock:

always #5 clk = ~clk;

Let’s look at this code in a little more detail. First, clk is a reg signal, so we need to assign it in a procedural block. We can create a procedural block using an always statement. We saw in the last tutorial that an always clock had an event specifier which told it when to activate – like on the positive edge of clock. This always block has no event. It always runs. Once it finishes, it just runs all over again. Every time the block runs, it inverts the clock. What is the magical #5? That tells the simulator to advance the clock 5 nanoseconds before performing the assignment. The units are in nanoseconds because of our timescale statement. So the clock starts at one, and then at five nanoseconds, it switches back to zero. In five more nanoseconds, it will switch back to one, and the process starts all over again, continuing forever. In effect, this is a 100MHz clock.

Instantiating the FPGA design

Now we need to declare a reg signal to drive the switch inputs and a wire signal sample the led outputs.

reg [7:0] switch;
wire [7:0] led;

An important feature of Verilog is that a module can contain instances of other modules. You can create hierarchical designs this way. This feature is really useful for reusing design components, or for just creating a sense of order in your designs. In the next line, we instantiate the FPGA top module in our test bench and wire up its inputs and outputs. To instantiate a module, you first mention the name of the module, then you give an instance name– that is, a name for this particular instance of the module. Often you might instantiate the same module more than once within your module. In these cases, you’ll need to give them different names. The simulator will refer to signals within nested modules in a similar manner to files nested within a directory structure. When doing so, the simulator will use the instance name, since the module name might be ambiguous if there are multiple instances of the module. An important thing to note is that module names and instance names use different namespaces. Because of this, there is never a situation where a name could refer to either a module or an instance. This means that the module name and the instance name can be the same. In my opinion, exploiting this actually leads to a more easily-understood design.

Anyway, after the instance name we give an open parenthesis and list the module connections. This takes the form of a period, followed by the port name within the module, then an open parenthesis and the name of the signal we are connecting up to the port. This is then followed by a close parenthesis, a comma, and finally the subsequent connections. All of these are followed by a close parenthesis and ended with a semicolon. Here is the module connections are supposed to look like:

top top (.clk(clk),.switch(switch),.led(led));

Driving the switches

We still haven’t told the simulator how to set the switch inputs during the simulation. What are our options here? Hypothetically, we could count through all 256 possible switch settings. In this case, that’s not a bad option. But you’ll often have designs where the sequence of inputs is important. If you were to count, the sequence would always be the same no matter how many times you counted. Because of this, you might not be able to spot bugs. Usually, the better approach is to use some type of randomness. This is what we’ll be doing here. Verilog has a built-in function called $random which you can use for this purpose. Here, we will make an always block which sets the switch inputs to a random value on every clock:

always @(posedge clk) switch = $random;

Finishing up

As it stands, we have a simulation which continues forever. That’s probably longer than we plan for our simulation to run. We can add some code that causes the simulation to end after a certain amount of time. The built-in Verilog task $finish will cause the simulation to complete. We could just have an initial block that waits for some fixed amount of time and then calls $finish like this.

initial #10000 $finish;

But that’s not something I prefer, because it will just wait for an amount of time to pass, and then kills the simulation. What if we made our clock period longer or shorter? The length of the test wouldn’t adjust to give us the same number of clock cycles. Instead I prefer to use the Verilog repeat construct and actually count the clock edges. That way, the simulation will always last the number of clock cycles I specify. In this case, I’ve chosen to run the simulation for 1000 clock cycles.

initial
  begin
    repeat (1000) @(posedge clk);
    $finish;
  end

The repeat statement tells the simulator to run the following statement 1000 times. The statement will simply wait for the rising edge of clk, and then, it will finish the simulation.

After this we just need to end the module with

endmodule

Running the simulation

To run the simulation, click on Run Simulation in the Flow Navigator window. This will bring up a drop-down menu. Select Run Behavioral Simulation. This should bring up a simulation waveform window which resembles the screen on a logic analyzer. It shows the changes in the simulation’s signals over time. On the left, you should see buttons that control the varying views of the simulation. You can zoom in on specific points in time, or zoom out to see a grand overview of the entire simulation. Go ahead and play around with the functions to get a feel for the tool.

In the next tutorial, I’m going to cover a few things that we glossed over in this tutorial. In particular, I’ll be talking about the difference between blocking and non-blocking assignment. It’s an important topic, so be sure to read that as well.

Here is my final version of bench.v for this tutorial:

`timescale 1ns/1ns
module bench;
  reg clk = 1;
  always #5 clk = ~clk;
  reg [7:0] switch;
  wire [7:0] led;
  top top(.clk(clk), .switch(switch), .led(led));
  always @(posedge clk) switch = $random;
  initial
    begin
      repeat (1000) @(posedge clk);
      $finish;
    end
endmodule

9 thoughts on “Tutorial 2: Simulation

  1. The above doesn’t always work if you want the SWITCH to propagate to the switch at the next clock edge. You need to use <= instead of = inside the always() blocks.

  2. Actually your assertion is not correct. The code does work correctly. However you are correct that there is a race condition between the assignments because they all use a blocking assign = rather than a non-blocking assign <=. The next tutorial goes into some detail on blocking and non-blocking assignments and there we learn why we would really want a non-blocking assignment in the always statements that assign the switch values. However, even knowing about non-blocking assignments we still want the assignment to clk to be blocking. Be sure to reply if you have further questions about this.

  3. Hi,
    I am getting this error when run simulation.
    ERROR: [XSIM 43-3250] File /home/rahul/Adeptchips/Learning/Vivado/Lab/Lab1/Lab1.srcs/sources_1/imports/lab_1_source/lab1.v, line 1. Unsupported named port connection association :

  4. Thank you very much for these superb tutorials. I am new to FPGA’s but thanks to these super nice and structured labs, I managed to ramp up quickly.

  5. Very helpful, thanks. I hope someday, that I could also write some tutorials to help others. But now, I need to build up myself in FPGA area.

  6. Hi there,

    Great tutorial series. Would like to ask why reg clk = 1? Why do you need to set this to 1?

    Cheers!

  7. We need to initialize the clock at the beginning of the simulation. If we don’t initialize it to 1 or 0 then it will just always remain X (unknown). I initialize to 1 rather than zero so that the rising edge of clock is on an even multiple of 10 boundary. No other reason.

    I’m glad you like the tutorials.

    -Pete

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.