Department of Computer Science and Technology

Course pages 2019–20

ECAD and Architecture Practical Classes

Exercise 0: SystemVerilog web tutor and tools introduction


You are expected to complete Step 1 in the first week and complete the other steps in the second week.

Step 1: Introduction to SystemVerilog via web tutor

Please login to our SystemVerilog web tutor to learn more about the SystemVerilog language. Completing all of the exercises will really help you complete the tickable exercises in a timely manner.


Fully implementing a system is very expensive, taking both time (FPGA: minutes/hours, ASIC: months/years) and money (ASIC: millions of dollars). Making a mistake - a system that does not function as intended - can thus be very costly. The later a bug is found, the more expensive it is to fix.

To reduce risk and save time and money, a key part of the design process is verification: ensuring correctness of the system. We wish to discover bugs as rapidly and cheaply as possible.

In these labs we'll apply a similar approach - verifying in simulation allows rapid development without having to wait for long FPGA build times.

Hardware/software systems are also very complicated, and so simulating a chip with billions of transistors at the level of basic physics would be intractable. For this reason we break down the design into pieces and simulate at the highest layer of abstraction sufficient to test the required properties. Abstractions are used to reduce overall complexity, and implementations can be tested against their abstractions.

Behavioural simulation
simulates the behaviour of a component without considering its implementation. For example, we can test a logic function obeys q=x+y without considering how it might be implemented in terms of half adders or NAND gates. In a behavioural simulation, it is possible to describe systems that may have no physical implementation, such as an infinite-length counter which never overflows.
Functional simulation
considers a particular implementation of the system, for example the circuit described in terms of logic gates, standard cells, transistors or silicon layout. As we go down the design process we can confirm that one level behaves the same as the previous level - for instance that a given transistor circuit does implement q=x+y within acceptable parameters.

When writing SystemVerilog, it is common to also write test harnesses to test the resultant hardware behaves correctly. These can make use of a number of non-synthesisable constructs. We'll introduce some of these to you here since they are used in the labs.

Four-valued logic

Signals can be other than high or low:

zHigh impedance

x appears in simulation for an uninitialised state - for example a register with unknown contents. Undefined values propagate: a combinational function f(a,b,c) returns x if any a, b or c are x. x has no physical realisation: the implementation will choose 0 or 1 (not always consistently).

z represents tristate, the third physical state of a wire, when we force neither a high or low voltage on it but instead disconnect it. Most commonly it is used for bidirectional pins, where we receive by tristating (not driving) the pin and listening if some other component is driving the pin high or low. Tristates are rarely used within a chip, but off-chip the I2C interface is an example of this usage.

Modelling delays

A simulation often wants to implement a fixed delay as part of a testbench, for example a delay of 5 nanoseconds.

`timescale 1ns / 1ps

module delay5ns (input logic in_signal, output logic delayed_signal);
  assign #5 delayed_signal = in_signal;

The timescale indicates we use units of 1 nanosecond, but with a resolution of 1 picosecond. In other words, if we wrote a time of 1.2345 it would be rounded to 1.235ns.

A delay like this has no physical meaning, but is often used to generate a clock in testbenches, for example to generate a 50MHz clock (10ns per half-cycle):

  always #10
    clk <= !clk;

When a design has a candidate implemention on a target platform (FPGA, ASIC) we might perform timing extraction which measures the gate and wire delays of the circuit as implemented. The process of back annotation then allows adjustment of these delays in the structural SystemVerilog to match the realised circuit, and we can then re-run our tests to check for correctness.

Event-driven simulation

A naive simulation would be to recalculate the state of all logic signals every time step. In the above code, we set a time resolution of 1 picosecond, ie 10-12 seconds. The naive simulator would thus perform 1 trillion iterations per second of simulated time - this would be very slow on a modern computer. Most of that would be inefficiently simulating logic states which are constant.

Instead event-driven simulation only simulates changes. Each gate is an object (with internal state). Signal or state changes are events with timestamps. Every object receives events and can decide to generate other events with a timestamp in the future, which are placed in a queue.

The simulator iterates over the event queue, picking the oldest event and passing to the appropriate object. If the output of that gate changes, an event with a new value and a future timestamp is placed in the queue. In this way, components which are idle take no simulation time.

The output of such a simulation is a list of (signal,timestamp) pairs of changes. These can be represented as a waveform - for instance in a Value-Change-Dump (.vcd) file - or on screen.

SystemVerilog system tasks

Additionally, simulations can have other statements such as:

$display("Value of foo = %d", foo); // print some text to the log (format string syntax as C/C++).

$display("Time is %t", $time); // print the simulation time.

$dumpfile("example.vcd"); $dumpvar; // dump all variables to a named .vcd file

$finish; // end the simulation

Step 2: Prepare to simulate your traffic light controller

In the last but one web tutor exercise, you designed a traffic light controller. In preparation for the tickable exercises, let's have a go at simulating your design using the ModelSim simulator you'll be using for the ticks. We're going to use this tool on an MCS Linux machine or within the course virtual machine.

First create a directory exercise0 for your simulation project and copy in your SystemVerilog code for your tlight module (including the header and footer). Put this into a file of the same name as the module with suffix .sv (for SystemVerilog), i.e.

Before we can simulate the design, we will need a test bench to provide input stimulus. In this instance there is just one input, the clock. We can provide a clock as follows and instantiate the tlight module to test it. Let's create a in which we will call the test bench tb_tlight and the instance of tlight to test dut (Design Under Test), viz:

`timescale 1ns / 1ps

module tb_tlight(
  output r,
  output a,
  output g);

  logic clk;      // clock signal we are going to generate

                  // instantiate design under test (dut)
  tlight dut(.clk(clk), .r(r), .a(a), .g(g));

  initial         // sequence of events to simulate
      clk = 0;   // at time=0 set clock to zero

  always #5       // every five simulation units...
    clk <= !clk;  // ...invert the clock

                  // produce debug output on the negative edge of the clock
  always @(negedge clk)
    $display("time=%05d: (r,a,g) = (%1d,%1d,%1d)",
      $time,      // simulator time
      r, a, g);   // outputs to display: red, amber, green

endmodule // tb_tlight

Step 3: Simulate your design

To start up ModelSim, we first need to setup paths and environment variables to use the ECAD tools, depending on your setup. Open a new terminal window. On the MCS you will need to source the appropriate setup.bash script as described in the setup instructions.

To start the ModelSim simulator, type:


This will open up a graphical window similar to the following:

The main ModelSim window consists of three areas. The "Transcript" area contains a command line terminal that you can use to give commands. All functions in ModelSim are accessible through the menus, but the command line can help in scripting up various sequences of operations that you wish to repeat. In addition, the Transcript area will show the output of any print statement that has been added to your component design.

On the left there is the library panel. This panel contains various libraries that contain one or more (compiled) components. To create a new library, click File->New->Library. ModelSim mandates that you create a library called "work" (Library Name and Library Physical Name), in which all custom compiled projects are stored. (If you already have a library 'work (unavailable)', right click it, go to New->Library and 'Create: a new library' with physical name 'work').

To compile your design in ModelSim, click on Compile->Compile.... From the file chooser dialogue, you can now navigate to the folder where you stored your sources. Double-click on the files that you wish to compile ( and When unsuccessful, the transcript area should give you some hints about why it failed to compile. For more info, you can also look in the compile summary, that can be opened by clicking Compile->Compile Summary. ModelSim is only able to figure out dependencies on other modules after they have been compiled, so either select all components by holding ctrl and click on all source files, or compile your components from the bottom up.

If everything went well, your components should now have appeared in the library "work" (you may need to click the + symbol by work to see the compiled modules). By double-clicking on the top-level component (tb_tlight), you will start a simulation. This will add the simulate buttons to the toolbar area above and insert another area in the ModelSim GUI called objects that contains all signals of the module selected in the "Sim" area, defaulting to the top level module.

There should be a "Wave" panel which contains no signals (if not click "View" and then "Wave" to show it). Add the signals r, a, g and clk from the "Objects" panel by dragging them to the "Wave" panel.

Now to simulate the design. Look for the simulation time period (typically 100ns) and the "Run" button to the right of it which looks like:

You might wish to press the zoom-in button a couple of times to get a simulation like the following.

In the "Transcripts" window you should also see the output from the $display statement in the test bench which should produce the following which provides the same information as the waveform output:

# time=    0: (r,a,g) = (x,x,x)
# time=   10: (r,a,g) = (1,0,0)
# time=   20: (r,a,g) = (1,1,0)
# time=   30: (r,a,g) = (0,0,1)
# time=   40: (r,a,g) = (0,1,0)
# time=   50: (r,a,g) = (1,0,0)
# time=   60: (r,a,g) = (1,1,0)
# time=   70: (r,a,g) = (0,0,1)
# time=   80: (r,a,g) = (0,1,0)
# time=   90: (r,a,g) = (1,0,0)
# time=  100: (r,a,g) = (1,1,0)

See the hints if your simulation produces x's as output.

Step 4: Script the simulation

During test driven hardware development, simulation runs are frequent and using the GUI to setup the simulation run each time is a bit tedious. The solution is scripting. ECAD tool vendors often use the TCL scripting language. Here is an example commented "do" script in TCL.

# set up the "work" library
vlib work
# compile our SystemVerilog files
# point the simulator at the compiled design
vsim work.tb_tlight
# add waveforms to the "Wave" pane
add wave -position insertpoint \
  /tb_tlight/r                 \
  /tb_tlight/a                 \
  /tb_tlight/g                 \
# run simulation for 200 nanoseconds
run 200 ns

If you copy the above script into a file and change ModelSim's working directory to match (the "Transcript" pane has a cd command), you can execute it by typing the following into the "Transcript" pane of ModelSim:


You can also run ModelSim (vsim) from the command-line only with no GUI. This can be useful when you just want the output from $display statements and no waveform viewer. For example:

vsim -c -do

Adding "quit" to the end of the script will cause vsim to cleanly exit after the simulation run. The "add wave ..." statement is redundant if there is no waveform window, so can be removed.


There is a ModelSim quick reference guide on the downloads page.

Optional exercise

Try out your electronic dice in ModelSim. You will need to provide a "button" input. One approach is to extend the initial block with changes of state to a "button" register using specified delays and non-blocking assignment, viz:

    clk = 0;
    button = 0;
    // after 20 simulation units (2 clock cycles given the clk configuration)
    #20 button = 1;
    // after 100 simulation units
    #100 button = 0;

Hints and Tips

You may find that your simulation produces x's as output due to registers being uninitialised at the start of the simulation. For FPGA designs we're sometimes lazy and rely on the FPGA to come up with registers reset to zero. This can be mimicked in simulation by adding an initial statement, e.g. for tlight you might add:

initial state=0;

When you change a file, you'll need to recompile it and restart simulation so that your changes take effect.

Initial statements are ignored when synthesising a design into a real circuit. Also note that this might not work if you assign to state elsewhere in an always_ff block, since the tools might not allow logic to be driven from two different blocks. A synthesisable (i.e. better) approach to initialising state is to use reset signals. For example, for tlight you might add another input called "rst" and for the always_ff block add:

always_ff @(posedge clk or posedge rst)
    state <= 3'b000;

In your test bench you'd need to add the rst signal as a register and initialise it for simulation viz:

  initial          // sequence of events to simulate
      clk = 0;     // at time=0 set clock to zero and reset to active (1)
      rst = 1;
      #20 rst = 0; // after 2 clock ticks set reset to inactive (0)

SystemVerilog is not a type-safe language, which means it will silently convert values of different types. One of the most annoying features for debugging is it will create undeclared signals (`implicit nets') for you if you don't define them. This can be problematic if you mistype a net name, because the correct version and the typo version will not be connected together, and the tools may synthesise these away. You can force an error to be generated with:

  `default_nettype none

in your source file. However this does not work for all compilation tools.