Writing Reusable Verilog Code using Generate and Parameters

In this blog post we look at the use of verilog parameters and the generate statement to write verilog code which is reusable. This includes examples of a parameterized module, a generate for block, generate if block and generate case block.

As with most programming languages, we should try to make as much of our code as possible reusable.

This allows us to reduce development time for future projects as we can more easily port code from one design to another.

We have two constructs available to us in verilog which can help us to write reusable code - parameters and generate statements.

Both of these constructs allow us to create more generic code which we can easily modify to suit our needs when we instantiate a component.

In the rest of this post, we look at both of these constructs in more detail.

Verilog Parameter

In verilog, parameters are a local form of constant which can be assigned a value when we instantiate a module.

As parameters have a limited scope, we can call the same verilog module multiple times and assign different values to the parameter. This allows us to configure the behaviour of a module on the fly.

As we discussed in the post on verilog modules, we must define an interface to a module when we write one.

We can then use this interface to interconnect a number of different modules within our FPGA design.

As a part of this interface, we can declare parameters as well as the inputs and outputs of the module.

The verilog code snippet below shows the method we use to declare a parameter in a module. When we declare a parameter in a verilog module like this, we call this a parameterized module.

module <module_name> #(
  parameter <parameter_name> = <default_value>
)
(
  // Port declarations
);

The <parameter_name> field in the verilog code above is used to give an identifier to our parameters.

We use this identifier to call the parameter value within our code, much like with a normal variable.

We can also assign a default value to our parameter using the <default_value> field in the example above.

This is useful as it allows us to instantiate the component without having to specifically assign a value to the parameter.

When we instantiate a module in a verilog design unit, we can assign a value to the parameter using either named association or positional association. This is exactly the same as assigning a signal to an input or output on the module.

However when we write code which uses the verilog 1995 standard, we can only use positional association to assign values to a parameter.

The verilog code snippet below shows the methods we use to assign a value to a parameter when instantiating a module.

// Example of named association
<module_name> # (
  // If the module uses parameters they are connected here
  .<parameter_name> (<parameter_value>)
)
<instance_name> (
  // port connections
);

// Example of positional association
<module_name> # (<parameter_values>)
<instance_name> (
  // port connections
);

Verilog Parameterized Module Example

In order to better understand how we use parameters in verilog, let's consider a basic example.

For this example, let's consider a design which requires two synchronous counters. One of these counters is 8 bits wide whilst the other is 12 bits wide.

To implement this circuit, we could write two different counter components which have different widths. However, this is an inefficient way of coding our circuit.

Instead, we will write a single counter circuit and use a parameter to change the number of bits in the output.

As it is not important to understanding how we use parameterized modules, we will exclude the functional code in this example.

Instead, we will look only at how we declare and instantiate a parameterized module in verilog.

The verilog code snippet below shows how we would write the interface for the parameterized counter module.

module counter #(
  parameter BITS = 8;
)
(
  input wire clock,
  input wire reset,
  output reg [BITS-1 : 0] count
);

In this example we see how we can use a parameter to adjust the size of a signal in verilog.

Rather than using a fixed number to declare the port width, we substitute the parameter value into the port declaration.

This is one of the most common use cases for parameters in verilog.

In the verilog code above, we defined the default value of the BITS parameter as 8.

As a result of this, we only need to assign the parameter a value when we want an output that isn't 8 bits.

The code snippet below shows how we would instantiate this module when we want a 12 bit output.

In this instance, we must over ride the default value of the parameter when we instantiate the verilog module.

counter # (
  .BITS (12)
) count_12 (
  .clock  (clock),
  .reset  (reset),
  .count  (count_out)
);

Although we use named association in the example above, we can also use positional association to assign values to a parameter in verilog.

The code snippet below shows how we would use positional association to assign the value of 12 to the BITS parameter.

counter # (12) count_12 (clock, reset, count_out);

Verilog Generate Statements

We use the generate statement in verilog to either conditionally or iteratively generate blocks of code in our design.

This allows us to selectively include or exclude blocks of code or to create multiple instances of a given code block.

We can only use the generate statement in concurrent verilog code blocks. This means we can't include it within always blocks or initial blocks.

In addition to this, we have to use either an if statement, case statement or a for loop in conjunction with the generate keyword.

We use the if and case generate statements to conditionally generate code whilst the for generate statement iteratively generates code.

We can write any valid verilog code which we require inside generate blocks. This includes always blocks, module instantiations and other generate statements.

The generate block was introduced in the verilog 2001 standard. As a result of this, we can't use this construct in verilog 1995 based designs.

Let's look at the three different types of generate block which we can use in our verilog designs.

Generate For Loop in Verilog

We can use a verilog for loop within a generate block to iteratively create multiple instances of a piece of code.

We typcially use the generate for loop approach to describe hardware which has a regular and repetitive structure.

For example, we may wish to describe a number of RAM modules which we want to control using a single bus.

If we use a generate block rather than manually instantiating all of the modules then we can reduce our code overhead.

The code snippet below shows the general syntax for the generate for block in verilog.

// Declare the loop variable
genvar <name>;

// Code for the generate block
generate 
  for (<initial_condition>; <stop_condition>; <increment>) begin
    // Code to execute
  end
endgenerate 

As we can see from this example, the syntax for this approach is virtually identical to the syntax we saw in the post on the verilog for loop.

However, there are two important differences between this approach and the normal for loops.

First of all, we must declare the loop variable using the genvar type.

The second difference is that we declare the loop within a generate block rather than a normal procedural block such as a verilog always block.

This difference is important as it alters the fundamental behaviour of the code.

When we write a generate for block we are actually telling the verilog compiler to create multiple instances of the code block.

In contrast, when we use the normal for loop we are telling the verilog complier to create a single instance of the code block but execute it multiple times.

As an example, let's look a very simple use case where we want to assign data to a 2 bit vector.

The verilog code below shows how we would do this using a generate for and a for loop. In both cases, the functionality of the code is the same but the structure produced is very different.

// Example using the for loop
always @(posedge clock) begin
  for (i = 0; i < 2; i = i + 1) begin
    sig_a[i] = 1'b0;
  end
end

// Example using the generate for block
generate
  for (i = 0; i < 2; i = i + 1) begin
    always @(posedge clock) begin
      sig_a[i] = 1'b0;
    end
  end
endgenerate

If we were to unroll the for loop example, we would get the code show below.

always @(posedge clock) begin
  sig_a[0] = 1'b0;
  sig_a[1] = 1'b0;
end

In constrast, unrolling the generate for code would result in the code shown below.

always @(posedge clock) begin
  sig_a[0] = 1'b0;
end

always @(posedge clock) begin
  sig_a[1] = 1'b0;
end

From this, we can see how the generate for is fundamentally different to the for loop.

Verilog Generate For Example

To better demonstrate how the verilog generate for statement works, let's consider a basic example.

For this example, we will use an array of 3 RAM modules which are connected to the same bus.

Each of the RAM modules has a write enable port, a 4-bit address input and 4-bit data input. These signals are all connected to the same bus.

In addition, each of the RAMs has a 4-bit data output bus and an enable signal, which are independent for each RAM block.

The circuit diagram shows the circuit we are going to describe.

Circuit diagram showing three RAM modules connected to a single bus.

We need to declare a 3 bit vector which we can then use to connect to the RAM enable ports. We can then connect a different bit to each of the RAM blocks based on the value of the loop variable.

For the data output bus, we could create a 12 bit vector and connect the read data output to different 4-bit slices of the vector.

However, a more elegant solution is to use an array which consists of 3 4-bit vectors. Again, we can then use the loop variable to assign different elements of this array as required.

The verilog code snippet below shows how we would code this circuit using the for generate statement.

// rd data array 
wire [3:0] rd_data [2:0];
  
// vector for the enable signals
wire [2:0] enable;
  
// Genvar to use in the for loop
genvar i;
  
generate 
  for (i=0; i<=2; i=i+1) begin
    ram ram_i (
      .clock    (clock),
      .enable   (enable[i]),
      .wr_en    (wr_en),
      .addr     (addr),
      .wr_data  (wr_data),
      .rd_data  (rd_data[i])
    );
  end
endgenerate

After synthesizing this code, we get the circuit shown below.

A circuit diagram showing 3 RAM blocks connected to a bus

If Generate Statement in Verilog

We use the generate if block in verilog to conditionally include blocks of verilog code in our design.

We can use the generate if statement when we have code that we only want to use under certain conditions.

One example of this is when we want to include a function in our design specifically for testing.

We can use a generate if statement to make sure that we only include this function with debug builds and not with production builds.

The code snippet below shows the general syntax for the verilog generate if statement.

generate
  if (<condition1>) begin
    // Code to execute
  end
  else if (<condition2>) begin
    // Code to execute
  end
  else begin
    // Code to execute
  end
endgenerate

As we can see from this example, the syntax for this approach is virtually identical to the syntax we saw in the post on the verilog if statement.

However, there is a fundamental difference between these two approaches.

When we write a generate if statement we are actually telling the verilog compiler to create an instance of the code block based on some condition.

This means that only one of the branches is compiled and any other branch is excluded from compilation. As a result of this, only one of the branches can ever be used within our design.

In contrast, when we use the if statement the entire if statement will get compiled and each branch of the statement can be executed.

Each time the if statement code is triggered during simulation, the condition is evaluated to determine which branch to execute.

Verilog Generate If Example

To better demonstrate how the verilog generate if statement works, let's consider a basic example.

For this example, we will write a test function which outputs the value of a 4-bit counter.

As this is a test function, we only need this to be active when we are using a debug build.

When we build a production version of our code, we tie the counter outputs to ground instead.

We will use a parameter to determine when we should build a debug version.

The code snippet below shows the implementation of this example.

// Use a parameter to control our build
parameter debug_build = 0;
 
// Conditionally generate a counter
generate
  if (debug_build) begin
    // Code for the counter
    always @(posedge clock, posedge reset) begin
      if (reset) begin
        count <= 4'h0;
      end
      else begin
        count <= count + 1;
      end
    end
  end
  else begin
    initial begin
      count <= 4'h0;
    end
  end
endgenerate

When we set the debug_build variable to 1, the synthesizer produces the circuit shown below. In this case, the synthesis tool has produced a four bit counter circuit.

A circuit diagram showing a four bit counter circuit

However, when we set the debug_build parameter to 0 then the synthesis tool produces the circuit shown below. In this instance, the synthesis tool has tied all bits of the count signal to ground.

A circuit diagram showing four buffers with their inputs tied to ground

Case Generate in Verilog

We use the generate case statement in verilog to conditionally include blocks of verilog code in our design.

The generate case statement essentially performs the same function as the generate if statement.

This means we can also use the generate case statement when we have code which we only want to include in our design under certain conditions.

For example, we could design a test function which we only want to include in debug builds.

We can then use the generate case statement to determine which version of the code gets built.

The code snippet below shows the general syntax for the generate case statement in verilog.

generate
  case (<variable>)
    <value1> : begin
      // This branch executes when <variable> = <value1> 
    end
    <value2> : begin
      // This branch executes when <variable> = <value2> 
    end
    default : begin
    // This branch executes in all other cases
    end
  endcase
endgenerate

As we can see from this example, the syntax for this approach is virtually identical to the syntax we saw in the post on the verilog case statement.

However, there is a fundamental difference between these two approaches.

When we write a generate case statement we are actually telling the verilog compiler to create an instance of the code block based on a given condition.

This means that only one of the branches is compiled and any other branch is excluded from compilation. As a result of this, only one of the branches can ever be used within our design.

In contrast, when we use the case statement the entire case statement will get compiled and each branch of the statement can be executed

Each time the case statement code is triggered during simulation, the condition is evaluated to determine which branch to execute.

Verilog Generate Case Example

To better demonstrate how the verilog generate case statement works, let's consider a basic example.

As the case generate statement performs a similar function to the if generate statement, we will look at the same example again.

This means that we will write a test function which outputs the value of a 4-bit counter.

As this is a test function, we only need this to be active when we are using a debug build.

When we build a production version of our code, we tie the the counter outputs to ground instead.

We will use a parameter to determine when we should build a debug version.

The verilog code below shows the implementation of this example using the generate case statement.

// Use a parameter to control our build
parameter debug_build = 0;
 
// Conditionally generate a counter
generate
  case (debug_build)
    1 : begin
      // Code for the counter
      always @(posedge clock, posedge reset) begin
        if (reset) begin
          count <= 4'h0;
        end
        else begin
          count <= count + 1;
        end
      end
    end
    default : begin
      initial begin
        count <= 4'h0;
      end
    end
  endcase
endgenerate

When we set the debug_build variable to 1, the synthesizer produces the circuit shown below. In this case, the synthesis tool has produced a four bit counter circuit.

A circuit diagram showing a four bit counter circuit

However, when we set the debug_build parameter to 0 then the synthesis tool produces the circuit shown below. In this instance, the synthesis tool has tied all bits of the count signal to ground.

A circuit diagram showing four buffers with their inputs tied to ground

Exercises

What is the benefit of using parameterized modules?

We can configure the functionality of the module when we instantiate it. This allows us to make our code easier to reuse.

What do we use generate blocks for in veirlog?

We use them to control the way that our designs are compiled and built. They allow us to conditionally include blocks of code in our design at compilation time.

What is the main difference between a for loop and a generate for block?

The generate for block is evaluated at compile time, meaning only one branch of the code block is ever compiled. All the code of a for loop is compiled and it is evaluated continuously during simulations.

Write a generate for block which instantiates 2 16 bit synchronous counters. The two counters should use the parameterized module example from earlier in this post.

// Variable for the generate loop
genvar i;

// Array for the outputs
wire [15:0] count_out [1:0]

// Generate the two counters
generate
  for (i=0; i < 2, i = i+1) begin
    counter # (
      .BITS (16)
    ) count_12 (
      .clock  (clock),
      .reset  (reset),
      .count  (count_out[i])
    );
  end
endgenerate 

Write a generate for block which instantiates either an 8 bit counter or a 16 bit counter, based on the value of a parameter. The two counters should use the parameterized module example from earlier in this post. You can use either a generate case or a generate if block to write this code.

// Parameter to control the generate block
parameter COUNT_16 = 0;

// Using a generate case statement
generate
  case (COUNT_16)
    0 : begin
      counter # (
        .BITS (16)
      ) count_16 (
        .clock  (clock),
        .reset  (reset),
        .count  (count16_out)
      );
    end
    default : begin
      counter # (
        .BITS (8)
      ) count_8 (
        .clock  (clock),
        .reset  (reset),
        .count  (count8_out)
      );
    end
  endcase
endgenerate

// Using a generate if statement
generate
  if (COUNT_16) begin
    counter # (
      .BITS (16)
    ) count_16 (
      .clock  (clock),
      .reset  (reset),
      .count  (count16_out)
    );
  end
  else begin
    counter # (
      .BITS (8)
    ) count_8 (
      .clock  (clock),
      .reset  (reset),
      .count  (count8_out)
    );
  end
endgenerate

Using Tasks and Functions in Verilog

In this post we look at how we use tasks and functions in verilog. Collectively, these are known as subprograms and they allow us to write verilog code which is reusable.

As with most programming languages, we should try to make as much of our verilog code as possible reusable. This allows us to reduce development time for future projects as we can more easily port code from one design to another.

Whilst functions should be familiar to anyone with experience in other programming languages, tasks are less common in other languages.

There are two main differences between functions and tasks.

When we write a verilog function, it performs a calculation and returns a single value.

In contrast, a verilog task executes a number of sequential statements but doesn't return a value. Instead, the task can have an unlimited number of outputs

In addition to this, verilog functions execute immediately and can't contain time consuming constructs such as delays, posedge macros or wait statements

A verilog task, on the other hand, can contain time consuming constructs.

We will discuss both of these constructs in depth in the rest of this post. This includes giving examples of how we write and call functions and tasks in verilog.

Verilog Function

In verilog, a function is a subprogram which takes one or more input values, performs some calculation and returns an output value.

We use functions to implement small portions of code which we want to use in multiple places in our design.

By using a function instead of repeating the same code in several places, we make our code more maintainable.

We write the code for functions in the verilog module which we will use to call the function.

The code snippet below shows the general syntax for a function in verilog.

// First function declaration style - inline arguments
function <return_type> <name> (input <arguments>); 
  // Declaration of local variables
  begin
    // function code
  end
endfunction

// Second function declaration style - arguments in body
function <return_type> <name>;
  (input <arguments>);
  // Declaration of local variables
  begin
    // function code
  end
endfunction

We must give every function a name, as denoted by the <name> field in the above example.

We can either declare the inputs inline with the function declaration or as part of the function body. The method we use to declare the input arguments has no affect on the performance of the function.

However, when we use inline declaration we can also omit the begin and end keywords if we want to.

We use the <arguments> field in the above example to declare the inputs to our function.

We use the <return_type> field to declare which verilog data type the function returns. If we exclude this part of the function declaration, then the function will return a 1 bit value by default.

When we return a value we do it by assigning a value to the name of the function. The code snippet below shows how we would simply return the input to a function. We can also simulate this example on EDA playground.

function integer easy_example (input integer a);
  easy_example = a;
endfunction

Rules for Using Functions in Verilog

Although functions are often fairly simple, there are a few basic rules which we must follow when we write a verilog function.

One of the most important rules of a function is that they can't contain any time consuming constructs such as delays, posedge macros or wait statements.

When we want to write a subprogram which consumes time we should use a verilog task instead.

As a result of this, we are also not able to call tasks from within a function. In contrast, we can call another function from within the body of a function.

As functions execute immediately, we can only use blocking assignment in our verilog functions.

When we write functions in verilog, we can declare and use local variables. This means that we can declare variables in the function which can't be accessed outside of the function it is declared in.

In addition to this, we can also access all global variables within a verilog function.

For example, if we declare a function within a module block then all of the variables declared in that module can be accessed and modified by the function.

The table below summarises the rules for using a function in verilog.

Rules for Using Functions in Verilog
Verilog functions can have one or more input arguments
Functions can only return one value
Functions can not use time consuming constructs such as posedge, wait or delays (#)
We can't call tasks from within a function
We can call other functions from within a function
Non-blocking assignment can't be used within a function
Local variables can be declared and used inside of the function
We can access and modify global variables from inside a verilog function
If we don't specify a return type, the function will return a single bit

Verilog Function Example

To better demonstrate how to use a verilog function, let's consider a basic example.

For this example, we will write a function which takes 2 input arguments and returns the sum of them.

We use verilog integer types for the input arguments and the return types.

We must also make use of the verilog addition operator in order to calculate the sum of the inputs.

The code snippet below shows the implementation of this example function in verilog.

As we have previously discussed, there are two methods we can use to declare verilog functions and both of these are shown in the code below.

We can also simulate this example using EDA playground.

// Using inline declaration of the inputs
function integer addition (input integer in_a, in_b);
  // Return the sum of the two inputs
  addition = in_a + in_b;
endfunction

// Declaring the inputs in the function body
function integer addition;
  input integer in_a;
  input integer in_b;
  begin
    // Return the sum of the two inputs
    addition = in_a + in_b;
  end
endfunction

Calling a Function in Verilog

When we want to use a function in another part of our verilog design, we have to call it. The method we use to do this is similar to other programming languages.

When we call a function we pass parameters to the function in the same order as we declared them. This is known as positional association and it means that the order we declare our arguments in is very important.

The code snippet below shows how we would use positional association to call the addition example function.

In the example below, in_a would map to the a argument and in_b would map to b.

// Calling a verilog function
func_out = addition(a, b);

Automatic Functions in Verilog

We can also use the verilog automatic keyword to declare a function as reentrant.

However, the automatic keyword was introduced in the verilog 2001 standard meaning that we can't write reentrant functions when working with the verilog 1995 standard.

When we declare a function as reentrant, the variables and arguments within the function are dynamically allocated. In contrast, normal functions use static allocation for internal variables and arguments.

When we we write a normal function, all of the memory which is used to perform the processing of the function is allocated only once. This is process is known as static memory allocation in computer science.

As a result of this, our simulation software must execute the function in it's entirety before it can use the function again.

This also means that the memory the function uses is never deallocated. As a result of this, any values stored in this memory will maintain their value between calls to the function.

In contrast, functions which use the automatic keyword allocate memory whenever the function is called. The memory is then deallocated once the function has finished with it.

This process is known as automatic or dynamic memory allocation in computer science.

As a result of this, our simulation software can execute multiple instances of an automatic function.

We can use the automatic keyword to write recursive functions in verilog. This means we can create functions which call themselves to perform a calculation.

As an example, one common use case for recursive functions is calculating the factorial of a given number.

The code snippet below shows how we would use the automatic keyword to write a recursive function in verilog. We can also simulate this example using EDA playground.

function automatic integer factorial (input integer a);
  begin
    if (a > 1) begin
      factorial = a * factorial(a - 1);
    end
    else begin
      factorial = 1;
    end
  end
endfunction

Verilog Task

We use verilog tasks to write small sections of code that we can reuse throughout our design.

Unlike functions, we can use time consuming constructs such as wait, posedge or delays (#) within a task. As a result of this, we can use both blocking and non-blocking assignment in verilog tasks.

Verilog tasks can also have any number of inputs and can also generate any number of outputs. This is in contrast to functions which can only return a single value.

These features mean tasks are best used to implement simple pieces of code which are repeated several times in our design. A good example of this would be driving the pins on a known interface, such as SPI or I2C.

We often write the code for tasks in the verilog module which will be used to call the task.

When we do this, we can also read or write any of the module's global variables inside of the task body.

We can also create global tasks which are shared by all modules in a given file. To do this we simply write the code for the task outside of the module declarations in the file.

The code snippet below shows the general syntax for a task in verilog.

As with functions, there are two ways in which we can declare a task but the performance of both approaches is the same.

// Task syntax using inline IO
task <name> (<io_list>);
  begin
    // Code which implements the task
  end
endtask

// Task syntax with IO declared in the task body
task <name>;
  <io_list>
  begin
    // Code which implements the task
  end
endtask

We must give every task a name, as denoted by the <name> field above.

When we write tasks in verilog, we can declare and use local variables. This means that we can create variables in the task which can't be accessed outside of the task it is declared in.

In addition to this, we can also access all global variables within a verilog task.

Unlike verilog functions, we can call another task from within a task. We can also make calls to functions from within a task.

Verilog Task Example

Let's consider a simple example to better demonstrate how to write a verilog task.

For this example, we will write a basic task which can be used to generate a pulse. The length of the pulse can be specified when we call the task in our design.

In order to do this, we must declare a single time type input in our task.

We will generate the pulse on a global reg type signal so there is no need to declare any outputs for the task.

The verilog code below shows the implementation of this example using the two different styles of task. We can also simulate this example on EDA playground.

// Global variable declaration
reg pulse;

// Task implementation using inline declaration of IO
task pulse_generate(input time pulse_time);
  begin
    pulse = 1'b1;
    #pulse_time pulse = 1'b0;
  end
endtask

// Task implementation with IO declared in body
task pulse_generate;
  input time pulse_time;
  begin
    pulse = 1'b1;
    #pulse_time pulse = 1'b0;
  end
endtask

Although this example is quite simple, we can see here how we can use the verilog delay operator (#) in a task. If we attempted to write this code in a function, this would cause an error when we tried to compile it.

We can also see from this example that we don't return a value in the same way as we do with a function.

Instead, we simply assign values to any signals that we have access to either as inputs or as global variables.

We can include and drive as many signals as we want when we write a task in verilog.

Calling a Task in Verilog

As with functions, we must call a task when we want to use it in another part of our verilog design.

The method we use to do this is similar to the method used to call a function.

However, there is one important difference between calling tasks and functions in verilog.

When we call a task in verilog, we can't use it as part of an expression in the same way as we can a function.

We should instead think of task calls as being a short hand way of including a block of code into our design.

As with functions, we use positional association to pass paramaters to the task when we call it.

This simply means that we pass parameters to the task in the same order as we declared them when we wrote the task code.

The code snippet below shows how we would use positional association to call the pulse_generate task which we previously considered.

In this case, the pulse_length input is mapped to the pulse_time variable and the pulse output is mapped to the pulse_out variable.

// Calling a task using positional association
generate_pulse(pulse_time);

Automatic Tasks in Verilog

We can also use the automatic keyword with verilog tasks in order to make them reentrant. Again, this keyword was introduced in the verilog 2001 standard meaning it can't be used with verilog 1995 compatible code.

As we talked about previously, using the automatic keyword means that our simulation tool uses dynamic memory allocation.

As with functions, tasks use static memory allocation by default which means that only one instance of a task can be run by the simulation software.

In contrast, tasks which use the automatic keyword allocate memory whenever the task is called. The memory is then freed once the task has finished with it.

Let's consider a basic example to show automatic tasks are used and how they differ from normals task.

For this example, we will use a simple task which increments the value of a local variable by a given amount.

We can then run this a number of times in a simulation tool to see how the local variable behaves using an automatic task and a normal task.

The code below shows how we write a static task to implement this example.

// Task which performs the increment
task increment(input integer incr);
  integer i = 1;
  i = i + incr;
  $display("Result of increment = %0d", i);
endtask
 
// Run the task three times
initial begin
  increment(1);
  increment(2);
  increment(3);
end

Running this code in the icarus verilog simulation tool results in the following output:

Result of increment = 2
Result of increment = 4
Result of increment = 7

As we can see from this, the value of the local variable i is static and stored in a single memory location.

As a result of this, the value of i is persistent and it maintains it's value between calls to the task.

When we call the task we are incrementing the value which is already stored in the given memory location.

The code snippet below shows the same task except that this time we use the automatic keyword.

// Automatic task which performs the increment 
task automatic increment(input integer incr);
  integer i = 1;
  i = i + incr;
  $display("Result of increment = %0d", i);
endtask
 
// Run the task three times
initial begin
  increment(1);
  increment(2);
  increment(3);
end

Running this code in the icarus verilog simulation tool results in the following output:

Result of increment = 2
Result of increment = 3
Result of increment = 4

From this we can now see how the local variable i is dynamic and is created whenever the task is called. After it has been created, it is then assigned the value of 1.

When the task has finished running, the dynamically allocated memory is freed and the local variable no longer exists.

Exercises

There are two main differences between tasks and functions, what are they?

A task can have ore than one output but a function can only have one. A function can not consume time but a task can.

What is the difference between an automatic function and a normal function in verilog?

Normal verilog functions use static memory allocation whereas automatic functions use dynamic memory allocation.

Write the code for a function which takes 3 integer inputs and returns the product of them.

function integer product(input integer a, b, c);
begin
  product = a * b * c;
endfunction : product

Write the code for a task which returns the sum of 2 numbers. However, the result should be delayed by a fixed amount of time before it is returned. The task should take three inputs - one of time type which sets the delay time and the 2 integers that will be summed together. In addition, the task should have one output which is the result of the summation.

task add_delay();
  // Declare the task IO
  input time del;
  input integer a;
  input integer b;
  output integer c;
  begin
    // Functional code of the task
    #del c = a + b;
  end
endtask

An Introduction to Loops in Verilog

In this post, we talk about the different types of loop which we can use in verilog - the for loop, while loop, forever loop and repeat loop.

As we saw in our previous post on sequential statements in verilog, there are a number of statements which we can only use within procedural blocks.

We use these statements to control the way that data is assigned in our verilog design.

The four different types of loop which we can use in verilog are also sequential statements which we use to assign data in our designs.

As a result of this, we can only write loops inside of procedural blocks such as an always block or initial block.

In the rest of this post, we talk about how each of these loops is used in verilog. We then consider a short example for each of these constructs to show how we use them in practise.

Loops in Verilog

We use loops in verilog to execute the same code a number of times.

The most commonly used loop in verilog is the for loop. We use this loop to execute a block of code a fixed number of times.

We can also use the repeat keyword in verilog which performs a similar function to the for loop. However, we generally prefer to use the for loop rather than the repeat keyword in verilog designs.

The other type of loop which we commonly use in verilog is the while loop. We use this loop to execute a part of our code for as long as a given condition is true.

Let's take a closer look at each of these types of loop.

Verilog forever loop

We use the forever loop in verilog to create a block of code which will execute continuously, much like an infinite loop in other programming languages.

This is in contrast to the other types of loop in verilog, such as the for loop and while loop, which only run a fixed number of times.

As we see saw in our previous post on verilog testbenches, one of the most common use cases for the forever loop is generating a clock signal in a verilog test bench.

The forever loop can not be synthesized meaning that we can only use it in our test bench code.

The code snippet below shows the general syntax for the verilog forever loop.

forever begin
  // Code to be executed by the loop goes here
end

Forever loop example

To better demonstrate how we use the forever loop in practise let's consider an example.

For this example we will generate a clock signal with a frequency of 10MHz which we could use inside of a test bench.

To do this, we firstly assign our signal to an initial value. We then use the forever block to invert the signal at regular intervals.

The code snippet below shows how we would implement this clock example in verilog.

 initial begin
    clk = 1'b0;
    forever begin
      #500 clk = ~clk;
    end
  end

There are two important things to say about this example.

Firstly, note that we use the verilog initial block which is another example of a procedural statement. Any code which we write in an initial block is executed once at the beginning of a simulation.

We almost always use initial blocks rather than always blocks in our testbench code. The reason for this is that they only execute once and we typically only need to run our test once.

The other important thing to note here is the use of the # symbol to model time delays in verilog.

In order for this example to work properly we would need to include the verilog timescale compiler directive in our code.

We use the timescale compiler directive to specify the time unit and resolution of our simulations.

In this instance, we need to set the time units to ns as is shown in the code snippet below.

`timescale 1ns / 1ps

Verilog repeat loop

We use the repeat loop to execute a given block of verilog code a fixed number of times.

We specify the number of times the code block will execute in the repeat loop declaration.

Although we most commonly use the repeat loop in verilog test benches, we can also use it in sythesizable code.

However, we have to take care when using this construct synthesizable code as we can only use it to describe repetitive structures.

The code snippet below shows the general syntax of the verilog repeat loop

repeat (<number>) begin
  // Code to be executed in the loop
end

We use the <number> field to determine how many times the repeat loop is executed.

The repeat loop is very similar to the for loop in verilog as they both execute code a fixed number of times.

The main difference between these two types of loop is that the for loop includes a local variable which we can reference inside the loop. The value of this variable is updated on every iteration of the loop.

In contrast, the repeat loop doesn't include this local loop variable. As a result of this, the repeat loop is actually less verbose than the for loop in instances where we don't need this variable.

Repeat Loop Example

The repeat loop is a relatively straight forward construct. However, let's consider a basic example to better demonstrate how it works.

For this example, let's suppose that we have a signal in our design that we want to toggle whenever there is a rising edge on another signal in our design.

The waveform below shows the functionality which we are trying to achieve in this example loop.

A waveform which shows a signal named sig_b being inverted every time there is a rising edge on a signal named sig_a.

However, we only want this toggle action to be effective a total of six times.

We can easily implement this in a repeat block, as shown in the code snippet below.

repeat (6) begin
  @(posedge sig_a)
  sig_b = ~sig_b;
end

We can see in this example that we have set the <number> field to 6. As a result of this, the repeat loop will run a total of six times before terminating.

We then use the posedge macro which we talk about in the post on the verilog always block. This macro tells us when a rising edge has occurred on the sig_a signal in our code.

In verilog we use the @ symbol to tell our code to wait for an event to occur.

This simply means that the code will pause at this line and wait for the condition in the brackets to evaluate as true. Once this happens, the code will carry on running.

In this example, we use this operator to block the execution of our repeat loop until a rising edge is detected on the sig_a signal.

Finally, we can use the not verilog bit wise operator (~) to invert the sig_b signal whenever a rising edge has been detected.

The waveform below shows the simulation result of this code, as taken from the icarus verilog simulator and output to the gtkwave wave viewer.

A simulation output which shows a signal named sig_b being inverted every time there is a rising edge on a signal named sig_a.

Verilog while Loop

We use the while loop to execute a part of our verilog code for as long as a given condition is true.

The specified condition is evaluated before each iteration of the loop.

As a result of this, all of the code in the block will execute in each valid iteration.

This happens even if the condition changes so that it no longer evaluates to true whilst the code in the block is running.

We can think of the while loop as an if statement that executes repeatedly.

As while loops are generally not synthesizable, we often use them in our testbenches to generate stimulus.

The code snippet below shows the general syntax for a while loop in verilog.

while <condition> begin
  // Code to execute
end

We use the <condition> field in the above construct to determine when the execution of the loop is stopped.

while loop Example

To better demonstrate how we use the while loop in verilog, let's consider a basic example.

For this example, we will create an integer type variable which is increased from 0 to 3. We then print the value of this variable on each iteration of the loop.

Although this is a trivial example, it demonstrates the fundamental principles of the while loop.

The code snippet below shows how we would implement this example. This example can also be simulated on EDA playground.

while (iter < 4) begin
  $display("iter = %0d", iter);
  iter = iter + 1;
end

This example assumes that the iter variable has already been declared and assigned an intial value of 0.

In every iteration of the loop, the second line of the code within the loop body increments the iter variable.

The <condition> field in this example is set so that the loop only executes when the iter variable is less than 4. As a result of this, the iter variable is incremented from 0 to 3 in this loop.

We use the $display system task, which we discussed in a previous post, to print the value of the iter variable on each iteration of the loop. The %0d operator indicates that the variable should be printed as a decimal number.

Verilog For Loop

When writing verilog code, we use the for loop to execute a block of code a fixed number of times.

As with the while loop, the for loop will execute for as long as a given condition is true. The specified condition is evaluated before each iteration of the loop.

We specify this condition as part of the for loop declaration. This condition is used to control the number of times the loop is executed.

Although it is commonly used in testbenches, we can also use the for loop in synthesizable verilog code.

When we use the for loop in synthesizable code, we typically use it to replicate sections of our hardware. One of the most common examples of this is a shift register.

As we previously mentioned, the for loop is very similar to the repeat loop. The main difference is that the for loop uses a local variable which can be used in our loop code.

The code snippet below shows the syntax we use in a verilog for loop.

  for (<initial_condition>; <stop_condition>; <increment>) begin
    // Code to execute
  end

We use the <initial_condition> field to set the initial value of our loop variable. We must declare the variable that we use in our loop before we can use it in our code.

The <stop_condition> field is the conditional statement which determines how many times the loop runs. The for loop will continue to execute until this field evaluates as false.

We use the <increment> field to determine how the loop variable is updated in every iteration of the loop.

Verilog for loop example

To better demonstrate how we use the for loop in verilog, let's consider a basic example.

For this example, we will write a simple four bit serial shift register using the verilog for loop. Implementing a shift register is actually one of the most common use cases of the for loop.

The shift register can be implemented using a simple verilog array.

We can then assign the input to the shift register to the first element of the array. We then use a for loop to shift the existing contents of the array to the left by one place.

The verilog code snippet below shows how we would implement this shift register using a for loop.

// The circuit input goes into the first register
shift[0] <= circuit_in;

// A for loop to shift the contents of the register
for (i = 1; i < 4; i = i + 1) begin
  shift[i] <= shift[i-1];
end

The first thing to notice in this code is that we use a loop variable (i) to reference an element of the array in our loop. We must declare this loop variable before we use it in our code.

As our shift array has four bits, we set the <stop_condition> field so that the loop executes only when the loop variable (i) is less than four.

Finally, we set the <increment> field so that the loop variable is incremented by one in every iteration. This allows us to iterate over every element in the array.

In this example, we make use of non-blocking assignment. The reason for this is that a shift register is an example of a sequential logic circuit.

Therefore, we would have to write this code inside of a clocked verilog always block to properly model a shift register.

Exercises

Which type of loop do we use to create code which runs continuously?

The forever loop executes continuously.

Which function do we normally use a forever loop to implement in a verilog testbench?

The forever loop is commonly used to implement a clock signal in a verilog testbench

What is the main difference between a for loop and a repeat loop?

The for loop includes a local loop variable which is incremented on every iteration of the loop.

Write a for loop which implements an 8 bit shift register.

// The circuit input goes into the first register
shift[0] <= circuit_in;

// A for loop to shift the contents of the register
for (i = 1; i < 8; i = i + 1) begin
  shift[i] <= shift[i-1];
end

Rewrite the previous exercise so that it is implemented using a while loop.

// The circuit input goes into the first register
shift[0] <= circuit_in;

// A while loop to shift the contents of the register
i = 1;
while (i < 8) begin
  shift[i] <= shift[i-1];
  i = i + 1;
end

If Statements and Case Statements in Verilog

In this post we talk about two of the most commonly used constructs in verilog - the if statement and the case statement.

We have seen in a previous post how use procedural blocks such as the always block to write verilog code which is executed sequentially.

We can also use a number of statements within procedural blocks which control the way that signals are assigned in our verilog designs. Collectively, these statements are known as sequential statements.

The case statement and the if statement are both examples of sequential statements in verilog.

In the rest of this post, we talk about how both of these statements are used in verilog. We then consider a short example for both of these constructs to show how we use them in practise.

Verilog If Statement

The if statement is a conditional statement which uses boolean conditions to determine which blocks of verilog code to execute.

Whenever a condition evaluates as true, the code branch associated with that condition is executed.

This statement is similar to if statements used in other programming languages such as C.

The verilog code snippet below shows the basic syntax for the if statement.

if (<expression1>) begin 
  // Code to execute
end
else if (<expression2>) begin
  // Code to execute
end
else begin
  // Code to execute
end

We can exclude the else and else if branches from the statement if we don't need them.

In fact, we have already seen this in the post on always blocks where we used the posedge macro to detect the rising edge of a clock signal.

We can include as many else if branches as necessary to properly model the underlying circuit.

The if statement uses boolean conditions to determine which lines of code to execute.

In the snippet above, these expressions are given by <expression1> and <expression2>.

These expressions are sequentially evaluated and the code associated with the expression is executed if it evaluates to true.

Only one branch of an if statement will ever execute. This is normally the first expression which evaluates as true.

The only exception to this occurs when none of the expressions are true. In this instance, the code in the else branch will execute.

When we omit the else branch in our if statement code then none of the branches will execute in this case.

The code associated with each branch can include any valid verilog code, including further if statements. This approach is known as nested if statements.

When using this type of code in verilog, we should take care to limit the number of nested statements as it can lead to difficulties in meeting timing.

If Statement Example

We have already seen a practical example of the if statement when modelling flip flops in the post on the verilog always block.

To demonstrate this construct more thoroughly, let's consider an example of a clocked multiplexor.

In this instance, we will use an asynchronously resettable D type flip flop to register the output of a multiplexor.

The circuit diagram below shows the circuit which we will use in this example.

A circuit diagram showing a two input multiplexor and a d type flip flop. The output of the multiplexor is the input to the flip flop.

The code snippet below shows how we implement this using a single always block and an if statement.

always @(posedge clock, posedge reset) begin
  if (reset) begin
    Q <= 1'b0;
  end
  else begin
    if (addr) begin
      Q <= b;
    end
    else begin
      Q <= a;
    end
  end
end

In this example, we use the first if statement to set the output of the flip flop to 0b whenever reset is active.

When the reset is not active, then the always block has been triggered by the rising edge of the clock. We use the else branch of the first if statement to capture this condition.

We use a second if statement to model the behaviour of the multiplexor circuit. This is an example of a nested if statement in verilog.

When the addr signal is 0b, we assign the output of the flip flop to input a. We use the first branch of the nested if statement to capture this condition.

We then use the else branch of the nested if statement to capture the case when the addr signal is 1b.

It is also possible for us to use an else-if type statement here but the else statement is more succinct. The behaviour is the same in both cases as the signal can only ever be 0b or 1b in a real circuit.

Verilog Case Statement

We use the verilog case statement to select a block of code to execute based on the value of a given signal in our design.

When we write a case statement in verilog we specify an input signal to monitor and evaluate.

The value of this signal is then compared with the values specified in each branch of the case statement.

Once a match is found for the input signal value, the branch associated with that value will execute.

The verilog case statement performs the same function as the switch statement in the C programming language.

The code snippet below shows the general syntax for the case statement in verilog.

case (<variable>)
  <value1> : begin
    // This branch executes when <variable> = <value1> 
  end
  <value2> : begin
    // This branch executes when <variable> = <value2> 
  end
  default : begin
    // This branch executes in all other cases
  end
endcase

It is possible to exclude the default branch of the statement, although this is not advisable. If the default branch is excluded then all valid values of the <variable> must have it's own branch.

As with the if statement, the code associated with each branch can include any valid verilog code.

This includes further sequential statements, such as if or case statements. Again, we should try to limit the number of nested statements as it makes it easier to meet our timing requirements.

Case Statement Example

To better demonstrate the way we use the case statement in verilog, let's consider a basic example.

For this example we will look at a simple four to one multiplexor circuit.

We frequently use the case statement to model large multiplexors in verilog as it produces more readable code than continuous assignment based implementations.

The circuit diagram below shows the circuit which we will use in this example.

A four input multiplexor

The code snippet below shows how we would implement this circuit using a case statement.

always @(*) begin
  case (addr)
    2'b00 : begin
      q = a;
    end
    2'b01 : begin
      q = b;
    end
    2'b10 : begin
      q = c;
    end
    default : begin
      q = d;
    end
  endcase
end

This example shows how simple it is to model a multiplexor using the case statement in verilog. In fact, the case statement provides the most intuitive way of modelling a multiplexor in verilog.

Although this example is quite straight forward, there are a few important points which we should consider in more detail.

The first thing to note in this example is that we use blocking assignment. The reason for this is that we are modelling combinational logic and non-blocking assignment normally leads to flip flops being placed in our design.

Another thing to note here is that we could remove the default keyword from this example. We would then explicitly list the value of addr required to output the value of d instead.

However, we have included the default keyword in this example to demonstrate how it should be used.

Exercises

Which blocks do we use to write sequential statements in a verilog design?

Sequential statements can only be written within a procedural block such as an always block or initial block.

Which keywords can we exclude from the if statement when they are not required?

We can exclude the else and else if keywords if they are not needed.

How many branches of the if statement can be executed at one time?

A maximum of one branch in an if statement can execute at any time.

When can we exclude the default branch from the case statement?

We can exclude the default branch if all valid values of the input signal are explicitly listed.

Use a case statement to write the code for a six to one multiplexor.

always @(*) begin
  case (addr)
    3'b000 : begin
      q = a;
    end
    3'b001 : begin
      q = b;
    end
    3'b010 : begin
      q = c;
    end
    3'b011 : begin
      q = d;
    end
    3'b100 : begin
      q = e;
    end
    default : begin
      q = f;
    end
  endcase
end

Rewrite the six to one multiplexor from the last exercise so that it uses an if statement.

always @(*) begin
  if (addr == 3'b000) begin
    q = a;
  end
  else if (addr = 3'b001) begin
    q = b;
  end
  else if (addr = 3'b010) begin
    q = c;
  end
  else if (addr = 3'b011) begin
    q = d;
  end
  else if (addr = 3'b100) begin
    q = e;
  end    
  else begin
    q = f;
  end
end

How to Write a Basic Verilog Testbench

In this post we look at how we use Verilog to write a basic testbench. We start by looking at the architecture of a Verilog testbench before considering some key concepts in verilog testbench design. This includes modelling time in verilog, the initial block, verilog-initial-block and the verilog system tasks. Finally, we go through a complete verilog testbench example.

When using verilog to design digital circuits, we normally also create a testbench to stimulate the code and ensure that it functions as expected.

We can write our testbench using a variety of languages, with VHDL, Verilog and System Verilog being the most popular.

System Verilog is widely adopted in industry and is probably the most common language to use. If you are hoping to design FPGAs professionally, then it will be important to learn this skill at some point.

As it is better to focus on one language as a time, this blog post introduces the basic principles of testbench design in verilog. This allows us to test designs while working through the verilog tutorials on this site.

If you are interested in learning more about testbench design using either verilog or SystemVerilog, then there are several excellent courses paid course available on sites such as udemy.

Architecture of a Basic Testbench

Testbenches consist of non-synthesizable verilog code which generates inputs to the design and checks that the outputs are correct.

The diagram below shows the typical architecture of a simple testbench.

Block diagram of a testbench, showing a stimulus block which generates inputs to the design and an output checker which checks for the correct outputs.

The stimulus block generates the inputs to our FPGA design and the output checker tests the outputs to ensure they have the correct values.

The stimulus and output checker will be in separate files for larger designs. It is also possible to include all of these different elements in a single file. 

The main purpose of this post is to introduce the skills which will allow us to test our solutions to the exercises on this site.

Therefore, we don't discuss the output checking block as it adds unnecessary complexity.

Instead, we can use a simulation tool which allows for waveforms to be viewed directly. The freely available software packages from Xilinx (Vivado) and Intel (Quartus) both offer this capability.

Alternatively, open source tools such as icarus verilog can be used in conjunction with GTKWave to run verilog simulations. 

We can also make use of EDA playground which is a free online verilog simulation tool.

In this case, we would need to use system tasks to monitor the outputs of our design. This gives us a textual output which we can use to check the state of our signals at given times in our simulation.

Instantiating the DUT

The first step in writing a testbench is creating a verilog module which acts as the top level of the test.

Unlike the verilog modules we have discussed so far, we want to create a module which has no inputs or outputs in this case. This is because we want the testbench module to be totally self contained.

The code snippet below shows the syntax for an empty module which we can use as our testbench.

module <module_name> ();

 // Our testbench code goes here

endmodule : <module_name>

After we have created a testbench module, we must then instantiate the design which we are testing. This allows us to connect signals to the design in order to stimulate the code.

We have already discussed how we instantiate modules in the previous post on verilog modules. However, the code snippet below shows how this is done using named instantiation.

<module_name> # (
  // If the module uses parameters they are connected here
  .<parameter_name> (<parameter_value>)
)
<instance_name> (
  // Connection to the module ports
  .<port_name> (<signal_name>), 
  .<port_name> (signal_name>)
);

Once we have done this, we are ready to start writing our stimulus to the FPGA. This includes generating the clock and reset, as well creating test data to send to the FPGA.

In order to this we need to use some verilog constructs which we have not yet encountered - initial blocks, forever loops and time consuming statements.

We will look at these in more detail before we go through a complete verilog testbench example.

Modelling Time in Verilog

One of the key differences between testbench code and design code is that we don't need to synthesize the testbench.

As a result of this, we can use special constructs which consume time. In fact, this is crucial for creating test stimulus.

We have a construct available to us in Verilog which enables us to model delays. In verilog, we use the # character followed by a number of time units to model delays.

As an example, the verilog code below shows an example of using the delay operator to wait for 10 time units.

#10

One important thing to note here is that there is no semi-colon at the end of the code. When we write code to model a delay in Verilog, this would actually result in compilation errors.

It is also common to write the delay in the same line of code as the assignment. This effectively acts as a scheduler, meaning that the change in signal is scheduled to take place after the delay time.

The code snippet below shows an example of this type of code.

// A is set to 1 after 10 time units
#10 a = 1'b1;

Timescale Compiler Directive

So far, we have talked about delays which are ten units of time. This is fairly meaningless until we actually define what time units we should use.

In order to specify the time units that we use during simulation, we use a verilog compiler directive which specifies the time unit and resolution. We only need to do this once in our testbench and it should be done outside of a module.

The code snippet below shows the compiler directive we use to specify the time units in verilog.

`timescale <unit_time> / <resolution>

We use the <unit_time> field to specify the main time unit of our testbench and the <resolution> field to define the resolution of the time units in our simulation.

The <resolution> field is important as we can use non-integer numbers to specify the delay in our verilog code. For example, if we want to have a delay of 10.5ns, we could simply write #10.5 as the delay.

Therefore, the <resolution> field in the compiler directive determines the smallest time step we can actually model in our Verilog code.

Both of the fields in this compiler directive take a time type such as 1ps or 1ns.

Verilog initial block

In the post on always blocks in verilog, we saw how we can use procedural blocks to execute code sequentially.

Another type of procedural block which we can use in verilog is known as the initial block.

Any code which we write inside an initial block is executed once, and only once, at the beginning of a simulation.

The verilog code below shows the syntax we use for an initial block.

initial begin
  // Our code goes here
end

Unlike the always block, verilog code written within initial block is not synthesizable. As a result of this, we use them almost exclusively for simulation purposes.

However, we can also use initial blocks in our verilog RTL to initialise signals.

When we write stimulus code in our verilog testbench we almost always use the initial block.

To give a better understanding of how we use the initial block to write stimulus in verilog, let's consider a basic example.

For this example imagine that we want to test a basic two input and gate.

To do this, we would need code which generates each of the four possible input combinations.

In addition, we would also need to use the delay operator in order to wait for some time between generating the inputs.

This is important as it allows time for the signals to propagate through our design.

The verilog code below shows the method we would use to write this test within an initial block.

initial begin
  // Generate each input to an AND gate
  // Waiting 10 time units between each 
  and_in = 2b'00;
  #10
  and_in = 2b'01;
  #10
  and_in = 2b'10;
  #10
  and_in = 2b'11;
 end

Verilog forever loop

Although we haven't yet discussed loops, they can be used to perform important functions in Verilog. In fact, we will discuss verilog loops in detail in a later post in this series

However, there is one important type of loop which we can use in a verilog testbench - the forever loop.

When we use this construct we are actually creating an infinite loop. This means we create a section of code which runs contimnuously during our simulation.

The verilog code below shows the syntax we use to write forever loops.

forever begin
  // our code goes here
end

When writing code in other programming languages, we would likely consider an infinite loop as a serious bug which should be avoided.

However, we must remember that verilog is not like other programming languages. When we write verilog code we are describing hardware and not writing software.

Therefore, we have at least one case where we can use an infinite loop - to generate a clock signal in our verilog testbench.

To do this, we need a way of continually inverting the signal at regular intervals. The forever loop provides us with an easy method to implement this.

The verilog code below shows how we can use the forever loop to generate a clock in our testbench. It is important to note that any loops we write must be contained with in a procedural block or generate block.

 initial begin
    clk = 1'b0;
    forever begin
      #1 clk = ~clk;
    end
  end

Verilog System Tasks

When we write testbenches in verilog, we have some inbuilt tasks and functions which we can use to help us.

Collectively, these are known as system tasks or system functions and we can identify them easily as they always begin wtih a dollar symbol.

There are actually several of these tasks available. However, we will only look at three of the most commonly used verilog system tasks - $display, $monitor and $time.

$display

The $display function is one of the most commonly used system tasks in verilog. We use this to output a message which is displayed on the console during simulation.

We use the $display macro in a very similar way to the printf function in C.

This means we can easily create text statements in our testbench and use them to display information about the status of our simulation.

We can also use a special character (%) in the string to display signals in our design. When we do this we must also include a format letter which tells the task what format to display the variable in.

The most commonly used format codes are b (binary), d (decimal) and h (hex). We can also include a number in front of this format code to determine the number of digits to display.

The verilog code below shows the general syntax for the $display system task. This code snippet also includes an example use case.

// General syntax
$display(<string_to_display>, <variables_to_display);

// Example - display value of x as a binary, hex and decimal number
$display("x (bin) = %b, x (hex) = %h, x (decimal) = %d", x, x, x);

The full list of different formats we can use with the $display system task are shown in the table below.

Format CodeDescription
%b or %BDisplay as binary
%d or %DDisplay as decimal
%h or %HDisplay as hexidecimal
%o or %ODisplay as octal format
%c or %CDisplay as ASCII character
%m or %MDisplay the hierarchical name of our module
%s or %SDisplay as a string
%t or %TDisplay as time

$monitor

The $monitor function is very similar to the $display function, except that it has slightly more intelligent behaviour.

We use this function to monitor the value of signals in our testbench and display a message whenever one of these signals changes state.

All system tasks are actually ignored by the synthesizer so we could even include $monitor statements in our verilog RTL code, although this is not common.

The general syntax for this system task is shown in the code snippet below. This code snippet also includes an example use case.

// General syntax
$monitor(<message_to_display>, <variables_to_display>);

// Example - monitor the values of the in_a and in_b signals
$monitor("in_a=%b, in_b=%b\n", in_a, in_b);

$time

The final system task which we commonly use in testbenches is the $time function. We use this system task to get the current simulation time.

In our verilog testbenches, we commonly use the $time function together with either the $display or $monitor tasks to display the time in our messages.

The verilog code below shows how we use the $time and $display tasks together to create a message.

$display("Current simulation time = %t", $time);

Verilog Testbench Example

Now that we have discussed the most important topics for testbench design, let's consider a compete example.

We will use a very simple circuit for this and build a testbench which generates every possible input combination.

The circuit shown below is the one we will use for this example. This consists of a simple two input and gate as well as a flip flip.

A circuit diagram showing a two input and gate with the output of the and gate being an input to a D type flip flop

1. Create a Testbench Module

The first thing we do in the testbench is declare an empty module to write our testbench code in.

The code snippet below shows the declaration of the module for this testbench.

Note that it is good practise to keep the name of the design being tested and the testbench similar. Normally this is done by simply appending _tb or _test to the end of the design name.

module example_tb ();

  // Our testbench code goes here

endmodule : example_tb

2. Instantiate the DUT

Now that we have a blank testbench module to work with, we need to instantiate the design we are going to test.

As named instantiation is generally easy to maintain than positional instantiation, as well as being easier to understand, this is the method we use.

The code snippet below shows how we would instantiate the DUT, assuming that the signals clk, in_1, in_b and out_q are declared previously.

example_design dut (
    .clock (clk),
    .reset (reset),
    .a     (in_a),
    .b     (in_b),
    .q     (out_q)
  );

3. Generate the Clock and Reset

The next thing we do is generate a clock and reset signal in our verilog testbench.

In both cases, we can write the code for this within an initial block. We then use the verilog delay operator to schedule the changes of state.

In the case of the clock signal, we use the forever keyword to continually run the clock signal during our tests.

Using this construct, we schedule an inversion every 1 ns, giving a clock frequency of 500MHz.

This frequency is chosen purely to give a fast simulation time. In reality, 500MHz clock rates in FPGAs are difficult to achieve and the testbench clock frequency should match the frequency of the hardware clock.

The verilog code below shows how the clock and the reset signals are generated in our testbench.

// generate the clock
  initial begin
    clk = 1'b0;
    forever #1 clk = ~clk;
  end

  // Generate the reset
  initial begin
   reset = 1'b1;
    #10
   reset = 1'b0;
  end

4. Write the Stimulus

The final part of the testbench that we need to write is the test stimulus.

In order to test the circuit we need to generate each of the four possible input combinations in turn. We then need to wait for a short time while the signals propagate through our code block.

To do this, we assign the inputs a value and then use the verilog delay operator to allow for propagation through the FPGA.

We also want to monitor the values of the inputs and outputs, which we can do with the $monitor verilog system task.

The code snippet below shows the code for this.

initial begin
    // Use the monitor task to display the FPGA IO
    $monitor("time=%3d, in_a=%b, in_b=%b, q=%2b \n", 
              $time, in_a, in_b, q);

    // Generate each input with a 20 ns delay between them
    in_a = 1'b0;
    in_b = 1'b0;
    #20 
    in_a = 1'b1;
    #20
    in_a = 1'b0;
    in_b = 1'b1;
    #20 
    in_a = 1'b1;
  end

Full Example Code

The verilog code below shows the testbench example in its entirety.

`timescale 1ns / 1ps

module example_tb ();
  // Clock and reset signals
  reg clk;
  reg reset;

  // Design Inputs and outputs
  reg in_a;
  reg in_b;
  wire out_q;

  // DUT instantiation
  example_design dut (
    .clock (clk),
    .reset (reset),
    .a     (in_a),
    .b     (in_b),
    .q     (out_q)
  );

  // generate the clock
  initial begin
    clk = 1'b0;
    forever #1 clk = ~clk;
  end

  // Generate the reset
  initial begin
   reset = 1'b1;
    #10
   reset = 1'b0;
  end

  // Test stimulus
  initial begin
    // Use the monitor task to display the FPGA IO
    $monitor("time=%3d, in_a=%b, in_b=%b, q=%2b \n", 
              $time, in_a, in_b, q);

    // Generate each input with a 20 ns delay between them
    in_a = 1'b0;
    in_b = 1'b0;
    #20 
    in_a = 1'b1;
    #20
    in_a = 1'b0;
    in_b = 1'b1;
    #20 
    in_a = 1'b1;
  end

endmodule : example_tb

Exercises

When using a basic testbench architecture which block generates inputs to the DUT?

The stimulus block is used to generate inputs to the DUT.

Write an empty verilog module which can be used as a verilog testbench.

module example_tb ();

  // Our test bench code goes here

endmodule : example_tb

Why is named instantiation generally preferable to positional instantiation.

It is easier to maintain our code as the module connections are explicitly given.

What is the difference between the $display and $monitor verilog system tasks.

The $display task runs once whenever it is called. The $monitor task monitors a number of signals and displays a message whenever one of them changes state,

Write some verilog code which generates stimulus for a 3 input AND gate with a delay of 10 ns each time the inputs change state.

`timescale 1ns / 1ps

intial begin
  and_in = 3'b000;
  #10 
  and_in = 3'b001;
  #10
  and_in = 3'b010;
  #10
  and_in = 3'b011;
  #10
  and_in = 3'b100;
  #10
  and_in = 3'b101;
  #10
  and_in = 3'b110;
  #10
end

Using the Always Block to Model Sequential Logic in Verilog

In this post, we discuss one of the most important constructs in verilog - the always block.

As we discussed in the post on verilog operators, there are two main classes of digital circuit which we can model in verilog – combinational and sequential.

In contrast to combinational logic, sequential circuits use a clock and require storage elements such as flip flops.

As a result, the output signals are synchronised to the circuit clock and changes do not occur immediately. 

We use the always block to write code which executes sequentially in verilog. This is crucial when describing sequential logic circuits in verilog.

As always, there are a number of exercises at the end of this post.

However, it is worth reading the blog on writing a basic testbench in verilog before tackling these exercises. This will allow us to simulate some of the circuits which we design in these exercises.

The Always Block in Verilog

When we write verilog, we use procedural blocks to create statements which are executed sequentially. Procedural blocks are particularly important for the modelling of sequential digital circuits.

In contrast, verilog continuous assignment statements execute concurrently (i.e. in parallel) in our designs. This matches the nature of the underlying circuits, which consist of a number of separate logic gates.

The always block is one of the most commonly used procedural blocks in verilog. Whenever one of the signals in the sensitivity list changes state, all of the statements in the always block execute in sequence.

The verilog code below shows the general syntax for the always block. We talk about the sensitivity list in more depth in the next section.

always @(<sensitivity_list>) begin 
  // Code to be executed goes here 
end

We need to be careful when using this construct as there are some features which are unique to verilog.

In particular, beginners often find it difficult to understand the way that signals are updated in an always block.

When we use always blocks, we can update the value of our signals either in parallel or sequentially. This depends on whether we use blocking or non-blocking assignment, which we discuss in more depth later in this post.

In order to be an effective verilog designer, it is important that we have a good understanding of the always block.

Let's look at some of the key features of the always block in more detail.

Sensitivity Lists

Any code which we write within an always block runs continuously. This means that the statements within the code block are executed in sequence until we reach the last line.

Once the last line in the sequence has been executed, the program then loops back to the first line. All of the statements in our always block are then executed in sequence again.

However, this behaviour is not representative of a real circuit which will remain in a steady state until one of the input signals changes state.

We use the sensitivity list in the alway block to emulate this behaviour.

To do this, the code within the always block will only execute after one of the signals in the sensitivity list changes state.

Flip Flop Example

Let’s consider how we would model a basic D type flip flop using the always block as an example.

As with all clocked flip flops, the output of a D type flip flop only changes state when there is a positive clock edge.

As a result of this, we include the clock signal in the sensitivity list so that the always block only executes when there is a rising edge on the clock signal.

The verilog code below shows how we would model a D type flip flop using the always block.

always @(posedge clock) begin
  q <= d;
end

In this code example, we use the posedge macro to determine when there is a transition from 0 to 1.

The single line of code within the always block is executed when this macro evaluates as true. This line of code assigns the value of D to the output signal (Q).

When we use the posedge macro in verilog, all other changes of state are simply ignored. This is exactly what we would expect from a D type flip flop.

Verilog also has a negedge macro which has the opposite functionality. When we use this macro, the always block will execute whenever the clock changes from 1 to 0.

We can also omit this macro altogether. In this case, the code executes whenever a signal in the sensitivity list changes state.

We should only ever use the posedge macro for clock signals in our verilog design. This is because synthesis tools will attempt to utilize clock resources within the FPGA to implement it.

Multiple Signals in a Sensitivity List

There are instances when we will want to include more than one signal in the sensitivity list.

A common example of this is when we write code to model the behaviour of flip flops with asynchronous resets.

When this is the case, we need the flip flop model to perform an action whenever the reset or clock signals change state.

To do this we simply list both of the signals inside the sensitivity list and separate them with a comma.

The code snippet below shows how we write such a flip flop.

always @(posedge clock, posedge reset) begin 
  if (reset) begin  
    q <= 1'b0; 
  end 
  else begin 
    q <= d; 
  end
end

As this example uses an active high reset, we again use the posedge macro in the sensitivity list.

An active high reset means that the reset is only active when it is equal to one.

We then use a construct known as an if statement to determine whether the always block triggered by the reset signal or the clock signal.

We will discuss the verilog if statement in a future blog post, although it’s functionality is fairly self explanatory.

When working with code which is verilog 1995 compatible we must separate the signals in the sensitivity list using the or keyword instead or a comma.

The code snippet below shows how we would model an asynchronously resettable flip flop using verilog 1995.

always @(posedge clock or posedge reset) begin 
  if (reset) begin  
    q <= 1'b0; 
  end 
  else begin 
    q <= d; 
  end
end

Blocking and Non-Blocking Assignment in Verilog

In the code examples we have seen so far in this series of posts, we have used two different types of assignment operators.

This is because verilog has two different types of assignment – blocking and non-blocking.

When we write code with non-blocking assignments we use the <= symbol whilst blocking code uses the = symbol.

When we use continuous assignment in verilog, we can only use blocking assignment.

However, we can use both types of assignment in procedural block.

Blocking assignment typically results in our synthesis tools implementing combinational logic circuits. In contrast, non-blocking assignment normally results in sequential circuits after synthesis.

Blocking assignment is the simplest of the two techniques to understand. When we assign signals using blocking assignment in verilog, our signals update their value as soon as the line of code is executed.

We commonly use this type of assignment to write combinational logic in verilog. However, in some circumstances we can use it to create sequential circuits.

In contrast, signals which use the non-blocking technique are not updated immediately after assignment. Instead, verilog uses assignment scheduling to update the values.

This is a little tricky to understand, so let’s consider it in a bit more depth.

Assignment Scheduling

When we write verilog code using non-blocking assignment, our code still executes sequentially. However, the signals which we are assigning do not get updated in this way.

To demonstrate why this is the case, let’s consider the twisted ring counter circuit below.

A simple twisted ring style counter circuit
always @(posedge clock) begin 
  q_dff1 <= ~q_dff2; 
  q_dff2 <= q_dff1; 
end

First, let’s look at the behaviour if the signals did update immediately.

If we assume that the output of both flip flops is 0b when a clock edge occurs, then the second line in the code will set the output of DFF1 to 1b.

We can then see that the line of code immediately beneath this would set the output of DFF2 to 1. This is clearly not the intended behaviour of this circuit.

To overcome this issue, non blocking assignment in verilog uses scheduled assignment.

As a result, changes to our signals don’t occur immediately after assignment but are instead scheduled to occur at some point in the future.

Normally, the signals update their value at the end of a simulation cycle. This refers to the time it takes the simulator to execute all of the code for a given time step.

To better demonstrate the way scheduled assignment works, let’s again consider the simple dual flip flop circuit.

When a rising edge is detected, the simulator firstly executes the statement to update DFF1. Once this line has been executed, an update to the output of DFF1 is scheduled to happen.

The simulator then runs the second line of code, this time using the original value of the DFF1 flip flop and schedules the update of DFF2. 

As there are only two statements in this design, the simulation cycle is now complete. At this point, all of the scheduled changes are applied and the values are updated for both flip flops.

Synthesis Example

To further demonstrate the difference between blocking and non blocking assignments in verilog, we will again model a basic two flip flop twisted ring counter circuit. The code snippet below shows he implementation of this circuit.

always @(posedge clock) begin 
  q_dff1 <= ~q_dff2;
  q_dff2 <= q_dff1; 
end

However, we can also look at the output of a synthesis tool such as vivado to see a diagram of the resulting circuit. The circuit diagram below shows this circuit.

We can see that there are two flip flops in the circuit whilst the not gate is implemented using LUT1.

Circuit diagram showing a 2 bit twisted ring circuit.

Now let’s take at look at the circuit we would get if we used blocking assignment in the code.

The verilog code below shows how we could (incorrectly) attempt to model this circuit using blocking assignment.

always @(posedge clock) begin 
  q_dff1 = ~q_dff2; 
  q_dff2 = q_dff1; 
end

This results in the circuit shown below after synthesis.

A circuit diagram showing a single flip flop with the output being passed through a look up table and back into the input.

We can see from this that using non blocking has resulted in the removal of the second flip flop from our circuit.

The reason for this should be fairly obvious, given what we have learnt about blocking assignment so far.

As the value of the q_dff2 is immediately assigned to the same value as q_dff1, our circuit model does not imply that there should be a flip flop in this signal path.

This example actually shows us one of the most important differences between blocking and non blocking assignment in verilog.

When we use non-blocking assignment, the synthesis tool will always place a flip flop in the circuit. This means that we can only use non blocking assignment to model sequential logic.

In contrast, we can use blocking assignment to create either sequential or combinational circuits.

However, we should only use blocking assignment to model combinational logic circuits in verilog. The main reason for this is that our code will be much easier to understand and maintain.

Combinational Logic in Always Blocks

Up to this point, we have only considered the modelling of sequential circuits using always block.

Although this is the most common use case, we can also model combinational logic using this approach.

As an example, the code below shows how we can use an always block to model the AND-OR circuit which we discussed in the post on continuous assignment in verilog.

// Using verilog 2001 style coding
always @(a, b, c) begin 
  logic_out = (a & b) | c; 
end

// Using verilog 1995 style coding
always @(a or b or c) begin 
  logic_out = (a & b) | c; 
end

We see that this code is almost identical to the example we looked at in the post on continuous assignment.

The only major difference here is the fact that we have encased it within an always block. We also remove the verilog assign keyword from the statement as we no longer need it.

We can also see from this example how the sensitivity list is more complex for combinational circuits than sequential circuits.

There are actually two methods which we can use to write the sensitivity list when modelling combinational logic circuits.

The first method we can use is to list each of the inputs to the circuit separated by either by the or keyword or by a comma. This is the method we have used in the example code above.

In addition to this, we can also use the * character to tell our verilog tools to automatically decide which signals to include in the sensitivity list.

This technique is preferable as it has the advantage of being easier to maintain. However, this method was introduced as part of the verilog 2001 standard meaning it can't be used with verilog 1995 code.

The code snippet below shows how we would use both of these methods.

// Sensitivity list with all signals listed 
always @ (a, b, c)
 
// Sensitivity list using the special character 
always @ (*)

Generally speaking, using the always block to model combinational logic adds boiler plate code to our design.

Therefore, we only use the always block to model combinational logic circuits in a few circumstances where it can simplify the modelling of complex combinational logic.

Multiplexors

One instance where it can be useful to use an always block to model combinational logic is when we want to model a multiplexor.

In this case, we can use a construct known as the case statement to model the multiplexor. In comparison to the methods we discussed in the post on modelling combinational logic in verilog, this provides a simpler and more intuitive way of modelling large multiplexors.

We talk about the verilog case statement in more detailed in a future blog post. However, the code snippet below shows how we would use the case statement to model a simple four to one multiplexor.

always @(*) 
  case (addr) begin 
    0 : begin 
      // This branch executes when addr = 0
      mux_out = a; 
    end 1 : begin 
        // This branch executres when addr = 1
        mux_out = b; 
    end 
    2 : begin
      // This branch executes when addr = 2
      mux_out = c; 
    end 
    3 : begin 
      // This branch executes when addr = 3
      mux_out = d; 
    end 
  endcase 
end

The case statement is fairly simple to understand, as it uses a variable to select one of a number of branches to execute.

We can include as many different branches as we require in the case statement.

In addition, we use the default branch to catch any values which we haven’t explicitly listed.

In order to use this as a multiplexor, we use the variable as if it were the address pins.

We can then assign the output of the multiplexor to the required value based on which branch we are executing.

Exercises

What is the difference between continuous assignment and procedural blocks (such as the always block) in verilog?

We use procedural blocks such as the always block to execute code sequentially in verilog. In contrast, continuous assignment is executed in parallel.

Why do we use sensitivity lists in the verilog always block?

They define the list of signals that an always will wait on before resuming the execution of code.

What is the difference between blocking and non-blocking assignment in verilog?

When we use blocking assignment all signal assignments take effect immediately. In contrast, when we use non-blocking assignment our signals are updated using assignment scheduling.

Which type of assignment can we use in continuous assignment? What about in procedural blocks?

When we write code using continuous assignment, we can only use blocking assignment.

We can use both types of assignment within a verilog procedural block. However, non-blocking assignment normally results in a sequential implementation after synthesis. In contrast to this, blocking assignment normally results in a combinational implementation.

Write the code for a 4 input NAND gate using an always block

always @(*) begin
  nand_out = a ~& b ~& c ~& d;
end

Write the code for the circuit shown below.

Circuit diagram showing 3 d type flip flops in a chain. The input to the first flip is an or gate whose inputs are the outputs of the other two flip flops.
always @(posedge clock) begin 
  q_dff1 <= q_dff2 | q_dff3; 
  q_dff2 <= q_dff1;
  q_dff3 <= q_dff2;
end

Using Continuous Assignment to Model Combinational Logic in Verilog

In this post, we talk about continuous assignment in verilog using the assign keyword. We then look at how we can model basic logic gates and multiplexors in verilog using continuous assignment.

There are two main classes of digital circuit which we can model in verilog – combinational and sequential.

Combinational logic is the simplest of the two, consisting solely of basic logic gates, such as ANDs, ORs and NOTs. When the circuit input changes, the output changes almost immediately (there is a small delay as signals propagate through the circuit).

In contrast, sequential circuits use a clock and require storage elements such as flip flops. As a result, output changes are synchronized to the circuit clock and are not immediate.

In this post, we talk about the techniques we can use to design combinational logic circuits in verilog. In the next post, we will discuss the techniques we use to model basic sequential circuits.

Continuous Assignment in Verilog

We use continuous assignment to drive data onto verilog net types in our designs. As a result of this, we often use continuous assignment to model combinational logic circuits.

We can actually use two different methods to implement continuous assignment in verilog.

The first of these is known as explicit continuous assignment. This is the most commonly used method for continuous assignment in verilog.

In addition, we can also use implicit continuous assignment, or net declaration assignment as it is also known. This method is less common but it can allow us to write less code.

Let's look at both of these techniques in more detail.

Explicit Continuous Assignment

We normally use the assign keyword when we want to use continuous assignment in verilog. This approach is known as explicit continuous assignment.

The verilog code below shows the general syntax for continuous assignment using the assign keyword.

assign <variable> = <value>;

The <variable> field in the code above is the name of the signal which we are assigning data to. We can only use continuous assignment to assign data to net type variables.

The <value> field can be a fixed value or we can create an expression using the verilog operators we discussed in a previous post. We can use either variable or net types in this expression.

When we use continuous assignment, the <variable> value changes whenever one of the signals in the <value> field changes state.

The code snippet below shows the most basic example of continuous assignment in verilog. In this case, whenever the b signal changes states, the value of a is updated so that it is equal to b.

assign a = b;

Net Declaration Assignment

We can also use implicit continuous assignment in our verilog designs. This approach is also commonly known as net declaration assignment in verilog.

When we use net declaration assignment, we place a continuous assignment in the statement which declares our signal. This can allow us to reduce the amount of code we have to write.

To use net declaration assignment in verilog, we use the = symbol to assign a value to a signal when we declare it.

The code snippet below shows the general syntax we use for net declaration assignment.

<type> <variable> = <value>;

The variable and value fields have the same function for both explicit continuous assignment and net declaration assignment.

As an example, the verilog code below shows how we would use net declaration assignment to assign the value of b to signal a.

wire a = b;

Modelling Combinational Logic Circuits in Verilog

We use continuous assignment and the verilog operators to model basic combinational logic circuits in verilog.

To show we would do this, let's look at the very basic example of a three input and gate as shown below.

A circuit diagram showing a three input and gate

To model this circuit in verilog, we use the assign keyword to drive the data on to the and_out output. This means that the and_out signal must be declared as a net type variable, such as a wire.

We can then use the bit wise and operator (&) to model the behavior of the and gate.

The code snippet below shows how we would model this three input and gate in verilog.

assign and_out = a & b & c;

This example shows how simple it is to design basic combinational logic circuits in verilog. If we need to change the functionality of the logic gate, we can simply use a different verilog bit wise operator.

If we need to build a more complex combinational logic circuit, it is also possible for us to use a mixture of different bit wise operators.

To demonstrate this, let's consider the basic circuit shown below as an example.

A logic circuit with the function (a and b) or c.

To model this circuit in verilog, we need to use a mixture of the bit wise and (&) and or (|) operators. The code snippet below shows how we would implement this circuit in verilog.

assign logic_out = (a & b) | c;

Again, this code is relatively straight forward to understand as it makes use of the verilog bit wise operators which we discussed in the last post.

However, we need to make sure that we use brackets to model more complex logic circuit. Not only does this ensure that the circuit operates properly, it also makes our code easier to read and maintain.

Modelling Multiplexors in Verilog

Multiplexors are another component which are commonly used in combinational logic circuits.

In verilog, there are a number of ways we can model these components.

One of these methods uses a construct known as an always block. We normally use this construct to model sequential logic circuits, which is the topic of the next post in this series. Therefore, we will look at this approach in more detail the next blog post.

In the rest of this post, we will look at the other methods we can use to model multiplexors.

Verilog Conditional Operator

As we talked about in a previous blog, there is a conditional operator in verilog. This functions in the same way as the conditional operator in the C programming language.

To use the conditional operator, we write a logical expression before the ? operator which is then evaluated to see if it is true or false.

The output is assigned to one of two values depending on whether the expression is true or false.

The verilog code below shows the general syntax which the conditional operator uses.

output = <expression> ? <value if true> : <value if false>;

From this example, it is clear how we can create a basic two to one multiplexor using this operator.

However, let's look at the example of a simple 2 to 1 multiplexor as shown in the circuit diagram below.

A 2 input multiplexor circuit

The code snippet below shows how we would use the conditional operator to model this multiplexor in verilog.

assign q = addr ? b : a;

Nested Conditional Operators

Although this is not common, we can also write code to build larger multiplexors by nesting conditional operators.

To show how this is done, let's consider a basic 4 to 1 multiplexor as shown in the circuit below.

A four input multiplexor

To model this in verilog using the conditional operator, we treat the multiplexor circuit as if it were a pair of two input multiplexors.

This means one multiplexor will select between inputs A and B whilst the other selects between C and D. Both of these multiplexors use the LSB of the address signal as the address pin.

assign mux1 = addr[0] ? b : a;
assign mux2 = addr[0] ? d : c;

To create the full four input multiplexor, we would then need another multiplexor.

This takes the outputs from the first two multiplexors and uses the MSB of the address signal to select between them.

The code snippet below shows the simplest way to do this. This code uses the signals mux1 and mux2 which we defined in the last example.

assign q = addr[1] ? mux2 : mux1;

However, we could easily remove the mux1 and mux2 signals from this code and instead use nested conditional operators.

This reduces the amount of code that we would have to write without affecting the functionality.

The code snippet below shows how we would do this.

assign q = addr[1] ? (addr[0] ? d : c) : (addr[0] ? b : a);

As we can see from this example, when we use conditional operators to model multiplexors in verilog, the code can quickly become difficult to understand. Therefore, we should only use this method to model small multiplexors.

Arrays as Multiplexors

It is also possible for us to use verilog arrays to build simple multiplexors.

To do this we combine all of the multiplexor inputs into a single array type and use the address to point at an element in the array.

To get a better idea of how this works in practise, let's consider a basic four to one multiplexor as an example.

The first thing we must do is combine our input signals into an array. There are two ways in which we can do this.

Firstly, we can declare an array and then assign all of the individual bits, as shown in the verilog code below.

// Assigning individual bits in the vector
assign in_vec[0] = a;
assign in_vec[1] = b;
assign in_vec[2] = c;
assign in_vec[3] = d;

Alternatively we can use the verilog concatenation operator, which allows us to assign the entire array in one line of code.

To do this, we use a pair of curly braces - { } - and list the elements we wish to include in the array inside of them.

When we use the concatenation operator we can also declare and assign the variable in one statement, as long as we use a net type.

The verilog code below shows how we can use the concatenation operator to populate an array.

// Using vector assignment
assign in_vec = {d, c, b, a};

// Declare and assign the vector in one line
wire [3:0] in_vec = {d, c, b, a};

As verilog is a loosely typed language, we can use the two bit addr signal as if it were an integer type. This signal then acts as a pointer that determines which of the four elements to select.

The code snippet below demonstrates this method in practise. As the mux output is a wire, we must use continuous assignment in this instance.

assign mux_out = in_vec[addr];

Exercises

What is the difference between implicit and explicit continuous assignment?

When we use implicit continuous assignment we assign the variable a value when we declare. When we use explicit continuous assignment we use the assign keyword to assign a value.

Write the code for a 2 to 1 multiplexor using any of the methods discussed we discussed.

// Using the conditional operator
assign mux_out = addr ? b : a;
 
// Using an array
wire in_arr [1:0] = {a, b} 
assign mux_out = in_arr[addr];

Write the code for circuit below using both implicit and explicit continuous assignment.

A logic circuit with the function (a or b) and c.
// Using explicit continuous assignment
wire logic_out;
assign logic_out = (a | b) & c;
 
// Using implicit continuous assignment
wire logic_out = (a | b) & c;

An Introduction to the Verilog Operators

In this post, we talk about the different operators which we can use in verilog. These operators provide us with a way to process the digital data in our verilog designs.

This processing can be extremely simple, as is the case with simple logic gates. However, we may also need to perform complex logical or mathematical operations on our data.

In any case, verilog provides us with a number of operators which allow us to perform a wide range of different calculations or operations on our data.

In most instances when we use verilog operators, we create boolean expressions or logic circuits which we want to synthesize. However, there are also some operators which we can't use to write synthesizable code.

Let's take a closer look at the various different types of operator which we can use in our verilog code.

Verilog Bit Wise Operators

We use the bit wise operators to combine a number of single bit inputs into a single bit output. In addition, we can also use the bit wise operators on verilog vector types.

We most commonly use the bit wise operators to model logic gates in verilog.

The table below shows the full list of bit wise operators which we can use in verilog.

Verilog Bit Wise Operators
OperatorDescription
~bit-wise NOT
&bit-wise AND
~&bit-wise NAND
|bit-wise OR
~|bit-wise NOR
^bit-wise XOR
~^bit-wise XNOR

The verilog code below shows how we use each of these operators in practise.

// Returns the value not a
y = ~a;

// Returns the value of a and b
y = a & b;

// Returns the value of a or b
y = a | b;

// Returns the value of a nor b
y = a ~| b;

// Returns the value of a nand b
y = a ~& b;

// Returns the value of a xor b
y = a ^ b;

// returns the value of a xnor b
y = a ~| b;

Verilog Arithmetic Operators

We use arithmetic operators to perform basic mathematic functions on our variables. These operators should already be familiar as they are mostly replications of common mathematic symbols.

However, these operators also require some consideration when we use them with synthesizable code.

The plus, minus and multiplication operators can all be synthesised by most modern tools.

However, this can often result in sub-optimal logical performance. As a result, it can be necessary to design logic circuits which specifically perform these functions.

Alternatively, we may wish to use DSP blocks within our FPGA to perform these operations more efficiently.

We should never use the modulus, exponential or divide operators for synthesizable code as most tools will be unable to handle them.

The table below shows the full list of arithmetic operators in Verilog.

Verilog Arithemtic Operators
OperatorDescription
+addition
-subtraction
*multiplication
/division
%modulus
**Exponential operator (introduced in verilog 2001)

The code snippet below shows how we use each of these operators in practise.

// Returns the value of a plus b
y = a + b;

// Returns the value of a minus b
y = a - b;

// Returns the value of a multiplied by b
y = a * b;

// Returns the value of a divided by b
y = a / b;

// Returns the modulus of a divided by b
y = a % b;

// Returns a to the power of b
y = a ** b;

Verilog Relational Operators

We use relational operators to compare the value of two different variables in verilog. The result of this comparison returns either a logical 1 or 0, representing true and false respectively.

These operators are similar to what we would see in other programming languages such as C or Java.

In addition to this, most of these operators are also commonly used in basic mathematics expressions so they should already feel familiar.

The table below shows the full list of relational operators in Verilog.

Verilog Relational Operators
OperatorDescription
>greater than
>=greater than or equal to
<less than
<=less than or equal to
==is equal to
!=is not equal to

The verilog code below shows how we use each of the relational operators in practise.

// 1 if a is greater than b
y = a > b;

// 1 if a is greater than or equal to b
y = a >= b; 

// 1 if a is less than b
y = a < b;  

// 1 if a is less than or equal to b
y = a <= b; 

// 1 if a is equal to b
y = a == b; 

// 1 if a is not equal to b
y = a != b;

Verilog Logical Operators

The verilog logical operators are similar to the bit-wise operators we have already seen.

However, rather than using these operators to model gates we use them to combine relational operators. As a result, we can build more complex expressions which can perform more than one comparison.

As with relational operators, these expressions return either a 1 (true) or 0 (false).

There are only three logical operators which we can use in verilog. Again, these are similar to operators which are used in languages such as C or Java.

The table below shows the full list of logical operators in Verilog.

Verilog Logical Operators
OperatorDescription
&&logical AND
||logical OR
!logical NOT

The verilog code below shows how we use each of the logical operators in practise.

Again, it is important that we use parentheses to separate the different elements in our expressions when using these operators.

// Returns 1 if a equals b and c equals d
y = (a == b) && (c == d);

// Returns 1 if a equals b or a equals c 
y = (a == b) || (a == c);

// Returns 1 if a is equal to b
y = !(a == b);

Verilog Shift Operators

In addition to the operators we have already seen, there are a few extra operators which we can use for specific logical functions.

One of the most useful and commonly used of these special functions are the shift operators, which are shown in the table below.

Verilog Shift Operators
OperatorDescription
<<shift left logical
>>shift right logical
<<<shift left arithmetic (introduced in verilog 2001)
>>>shift right arithmetic (introduced in verilog 2001)

When designing digital circuits, we frequently make use of shift operations. As a result, verilog provides us with a simple technique for implementing these functions.

The shift operator actually requires two arguments. The first of these is the name of the signal which we want to shift. The second argument is the number of bits we want to shift.

When we use the logical shift operators, all the blank positions are filled with 0b after the signal has been shifted by the required number of bits.

In contrast, the arithmetic shift operators preserve the sign of the shifted signal. As a result of this, they should only be used with the verilog signed types.

The code snippet below shows how we use the shift operators in practise.

// Shift the a signal left by 3 bits
a = a << 3;

// Shift the b signal right by 8 bits
b = b >> 8;

// Shift the a signal left by 2 bits using arithmetic operator
// In this instance we use casting to make c a signed type
c = $signed(c) <<< 3;

// Shift the d signal right by 5 bits using arithmetic shift operator
// In this instance we assume d is already a signed type
d = d >>> 5;

Verilog Conditional Operator

In verilog, we use a construct known as the conditional operator to assign data to a signal based on a conditional statement.

To use the conditional operator, we write a logical expression before the ? operator which is then evaluated to see if it is true or false.

The output is assigned to one of two values depending on whether the expression is true or false.

This operator may already be familiar as it is also used in other programming languages such as C and Java. However, in this case it is known as the ternary operator.

The code snippet below shows the general syntax for the verilog conditional operator.

output = <condition> ? <true> : <false>

When the expression given in the <condition> field evaluates as true, then the output is set to the value given in the <true> field.

If the conditional expression evaluates as false, then the output is set to the value given by the <false> field.

The code snippet below shows a practical example of the verilog conditional operator. In the a future post in this series, we see how we can use the conditional operator model multiplexors.

// Assign a to the value of c when it is greater than b 
a = c > b ? c : b;

Concatenation and Replication Operators

The final types of verilog operator which we can use are the concatenation and replication operators.

In both instances, the output of these operators are a vector type. However, the inputs to both of these operators can be either single bit or vector types.

Both of these verilog operators are show in the table below.

Verilog Concatenation Operator
OperatorDescription
{ }Concatenation operator
{{ }}Replication operator

We use the verilog concatenation operator to combine two or more signals into a vector.

As an example, we may have 2 single bit signals which we want to combine to use as an address for a multiplexor.

To use the concatenation operator, we list the signals which we wish to combine within the curly brackets. We separate this list of signals using a comma.

When we use the verilog concatenation operator, the bits in the output match the order in which they are listed inside the brackets.

For example, the code snippet below would result in a output vector which has the value 0011b.

c ={ 2'b00, 2'11};

We use the replication operator to assign the same value to a number of bits in a vector.

For example, if we wanted to assign all of the bits of a vector to 0b then we would use the replication operator.

When we use the replication operator we have to specify both the signal or value we want to replicate and the number of times we want to replicate it.

The verilog code below show how we use the concatenation and replication operators in practice.

// Combine the signal a and b into a vector using concatenation
c = {a, b};

// Replicate signal c 3 times
d = { 3{c} };

Exercises

Which type of operators do we use to model logic gates in verilog?

We use the bit wise operators to model logic gates in verilog.

Two of the arithmetic operators should not be used with synthesizable code – name them.

The division and modulus operators can’t be synthesized.

What is the difference between the bit wise and logical operators?

The bit wise operators work on individual bits whereas the logical operators are used to combine logical expressions.

What is the difference between the logical shift operators and the arithmetic shift operators.

The logical shift operators pad the blank positions with 0b whereas the arithmetic operator preserves the sign of the signal.

An Introduction to Verilog Data Types and Arrays

In this post, we talk about the most commonly used data types in Verilog. This includes a discussion of data respresentation, net types, variables types, vectors types and arrays.

Although verilog is considered to be a loosely typed language, we must still declare a data type for every port or signal in our verilog design.

The type which we specify is used to define the characteristics of our data.

We can use types which interpret data purely as logical values, for example. We can also use types which interpret our data as if it were a numeric value.

When we assign data to a signal in verilog, the data is implicitly converted to the correct type in most cases. As a result, there is often no need necessary to explicitly perform type conversions in verilog.

Respresenting Data in Verilog

When we write verilog, we often need to represent digital data values in our code. We can express this data as either a binary, hexadecimal or octal value.

Unlike in other programming languages, we also need to define the number of bits we have in our data representation.

This is because we are fundamentally describing hardware circuits when we use verilog. Therefore, we can create data busses which contain as many bits as we choose.

The code snippet below shows the general syntax for representing digital data in verilog.

<bits>'<representation><value>

We use the <bits> field to indicate the number of bits in the data that we are representing.

We use the <representation> field to indicate how our data is represented. This field can be set to b (for binary), h (for hex), o (for octal) or d (for decimal).

Finally, we use the <value> field to set the actual value of the data.

The code snippet below shows how we represent the decimal value of 8 using each of the different valid reprentations.

// Binary value of 8
4'b1000;
// Hex value of 8
4'h8;
// Octal value of 8
4'o10;
// Decimal value of 8
4'd8

Basic Data Types in Verilog

Broadly speaking, the basic data types in verilog can be split into two main groups - net types and variable types.

We use these two different groups to model different elements of our digital circuits.

We use the net types to model connections in our digital circuits. They are unable to store values on their own and must be driven with data.

We primarily use the variable types to model registers or flip flops in our design. These types can store data, meaning that their behaviour is similar to variables in other programming languages such as C.

Regardless of the exact type we are using, there are four valid values we can assign to individual bits in our data. These four different values are shown in the table below.

0Binary 0 value
1Binary 1 value
zHigh impedance value
xunknown value

We use the same syntax to declare a variable in verilog, regardless of the exact type. The code snippet below shows this general syntax.

// General syntax to declare a variable in verilog
<type_name> <size> <variable_name> = <value>;

We use the <type_name> field in the above example to declare the type of variable we have. We simply replace this field with the name of the type.

As an example, the verilog code below declares an integer type variable and assigns it a value of 100.

integer example = 100;

Net Types in Verilog

We use the net data types in verilog to describe the physical connections between different components in our design. As a result of this, net types on their own can not be used to store data values or drive data.

To better demonstrate when we would use a net type, consider the circuit diagram shown below.

In this circuit, we would use a net type to connect the output of the multiplexor to the input of the flip flop.

A circuit diagram showing a two input multiplexor and a d type flip flop. The output of the multiplexor is the input to the flip flop.

We normally use continuous assignment to drive data onto a wire type. To do this we must use the assign keyword, as shown in the code snippet below. We talk about continuous assignment in more detail in a later post.

// Driving a net type to a constant 0 using the assign keyword
assign a = 1'b0;

We can not use net types in procedural code such as always blocks. The always block is discussed in more detail in a later blog post.

Wire Type in Verilog

The most commonly used net type in verilog is the wire type which we discussed in the previous post.

We use the wire type in verilog to declare signals which are very basic point to point connections in our design. As the name suggests, they are roughly equivalent to an electrical wire in a traditional circuit.

The verilog code below shows how we use the wire type together with the assign keyword.

// Declaration of a single wire
wire a;

// Driving data onto the wires using assign
assign a = c;
assign b = d;

wand & wor Types

Although the wire type is the most commonly used of the net data types, there are several other types of net which we can use in our verilog designs.

The wand and the wor net types are used to insert basic logic gates into our circuit. We use the wand to insert an and gate and the wor type to create an or gate.

When we use the wand and wor types, we must assign the signal more than once. We do this as each of the assignments represents one input to the underlying logic gate.

The verilog code below shows how we use the wand and wor types together with the assign keyword.

// Declaration of our wand and wor types
wor a;
wand b;

// Wires which connect to our gates
wire c, d, e, f;

// Create an or gate with the function c or d
assign a = c;
assign a = d;

// create an and gate with the function e and f
assign b = e;
assign b = f;

As we will see in a later post, we can easily use the wire type to model combinational logic in verilog. As a result of this, the use of the wor and wand types is not recommended.

tri, triand & trior Types

In addition to the wire, wand and wor state, we can also use an equivalent tri, triand or trior type.

We use these types in the exact same way as the wire, wand and wor types. In fact, the functionality of these types is exactly the same. However, we can use them to more clearly show the intent of our design.

The code snippet below shows a basic example where the tri type is driven to high impedance.

// Declaration of our tri type
tri a;

// Drive the tri type to high impedance
assign tri = 1'bz;

However, as the wire type can also can take tristate values, we rarely use the tri type in practise. The same is also true with the trior and triand types, which can also easily be replicated using the wire type in our verilog designs.

supply0 & supply1 Types

The final net types which we can use in our verilog designs are the supply0 and supply1 types.

We can use these types to tie our signal to a constant value of either binary 1 or 0. As this has the effect of creating a net which is tied to either ground or Vcc, we don't need to assign any data to this type.

The code snippet below shows how we use these types to create a signal which is tied either high or low.

// Create a net which is tied to 0b
supply0 a;

// Create a net which is tired to 1b 
supply1 b;

However, we rarely need to tie a signal high or low in our design and when we do, it is simple to accomplish using a wire type. Therefore, the supply0 and supply1 types are rarely used in practise.

Variable Types in Verilog

Unlike net types, we use variable data types in verilog to store values. When we assign a value to a variable type it maintains this value until it is assigned again.

The variable types are generally more intuitive to understand than net types as they behave in a similar manner to variables in languages such as C.

To better demonstrate when we would use a variable type, consider the circuit diagram shown below.

In this circuit, we would use a variable type to model the flip flop output as it effectively stores a single bit of data.

A circuit diagram showing a two input multiplexor and a d type flip flop. The output of the multiplexor is the input to the flip flop.

We must use variable types within blocks of procedural code such as an always block, as shown in the code snippet below which models a D type flip flop.

always @(posedge clock)
  q <= d;
end

Reg Type in Verilog

The most commonly used variable type in verilog is the reg type. We can use this type whenever we need to store a value in our design.

We most commonly use the reg type to model the behaviour of flip flops.

However, the reg type can also be used to model combinational logic in verilog in some circumstances.

We discuss the use of the reg type for modelling both types of logic in more detail in the post on the verilog always block.

The verilog code snippet below shows how we use the reg type to model a basic flip flop.

// Declaration of our reg types
reg q;

// Code for a basic flip flop
always @(posedge clock)
  q <= d;
end

Numeric Variable Types

The types which we have looked at so far are all used with single bits of data. However, we can also represent data numerically in our verilog designs.

In verilog, there are two commonly used numeric types - the integer type and the real type. Let's take a closer a look at both of these types.

Verilog Integer Type

The most commonly used type for numerical data in verilog is the integer type. However, we normally use this for internal signals in a module rather than for ports. 

By default, the integer is a 32 bit 2s complement number which we can use to represent any whole number in our verilog design.

When we use an integer type, we assign numerical rather than binary values to the variable.

As we can also assign numeric values to the reg type, we typically use integers for constants or loop variables in verilog.

Our synthesis tools will automatically trim any unused bits in our integer type. For example, if we declare an integer constant with a value of 255 then our synthesis tool will trim this down to 8 bits.

The code snippet below shows how we declare and assign an integer type in verilog.

// Example of an integer
integer a = 255;
Verilog Real Type

In addition to the integer type, we can also use the real type in verilog. We use this type to store non-integer numbers, i.e. numbers which also have a decimal part.

The real type is typically implemented as a 64 bit floating point number in verilog. As a result of this, it can't be directly synthesized and we typically only use the real type in our verilog testbenches.

We can use either decimal or scientific notation to assign values to the real type.

The code snippet below shows how we declare a real type and assign data to it.

// Declaration of a real type
real a;

// Assign of data using decimal notation
a = 2.5;

// Assignment of data using engineering notation
a = 1e-3;

Vector Types in Verilog

With the exception of the numerical types, all of the types which we have looked at so far consist of a single bit.

However, we often use data busses to transfer data within a digital circuit.

In verilog, we can use vector types to create data buses. This allows us to declare a signal which has more than one bit.

The code snippet below shows the general syntax which we use to declare a vector type in verilog.

// General syntax to declare a vector type
<type> <size> <variable_name>;

When we define the size of the vector we must specify the most significant and least significant bits (MSB and LSB). Therefore, the <size> field takes the form [MSB:LSB].

For example, to declare a 4 bit little endian type vector we would use the construct [3:0].

As we talked about earlier in this post, we can represent data using binary, hex, octal or decimal formats. When we assign data to a vector we can use any of these representations.

The verilog code below shows how we would declare a 4 bit wide reg type. We also see how we can use the different data representations to assign the value of 1010b to the variable.

// Declare our reg type vector
reg [3:0] a;

// Assign binary data
a = 4'b1010;

// Assign hex data
a = 4'ha;

// Assign decimal data
a = 4'd10;

// Assign octal data
a = 4'o12;

Signed and Unsigned Data in Verilog

Prior to the release of the verilog 2001 standard all variable and net types could only be used to store unsigned data types.

Similarly, the integer type was always interpreted as a signed value.

However, the signed and unsigned keywords were introduced as a part of the verilog 2001 standard. This allows us to change the way our variable interprets data.

When we declare a type as signed in verilog, it is interpreted as a 2's complement number. This means that we can assign negative numbers to these signals.

By default, the integer type is signed whilst both the reg and wire types are unsigned. We only need to use these keywords if we wish to modify this default behaviour.

The verilog code below shows how we can declare signed and unsigned data using the reg, wire and integer types. In this case, all of the variables which we declare are 32-bits wide.

// Declarations for signed and unsigned reg type
reg [31:0] a;
reg signed [31:0] b;

// Declaration for signed and unsigned wire type
wire [31:0] a;
wire signed [31:0] b;

// Declaration for signed and unsigned wire type
integer unsigned a;
integer b;

Arrays in Verilog

We can also create and use array types in verilog. These are particularly useful in the modelling of memories.

In order to declare an array in verilog, we simply add an extra field after the variable name which declares how many elements there are in our array.

This declaration takes the same format as the vector size field which we talked about previously.

The code snippet below shows the general syntax which we use to declare an array type in verilog. We use the <elements> field to declare the size of our array.

// General syntax to declare an array type
<type> <size> <variable_name> <elements>;

As an example, let's say we want to create an array of 3 bit reg types. We want to have a total of 8 elements in the array. The verilog code below shows how we would create this array.

reg [2:0] example [7:0];

We can access individual elements in the array type using square brackets. For example, the verilog code below shows how we would assign the value of 5h to the final element in our example array.

example[7] = 3'h5;

We can also simulate this example on EDA playground.

Multi Dimensional Arrays

In the verilog 1995 standard, it is only possible for us to create one dimensional arrays such as those we used in the previous section.

However, we can also create arrays which have more than one dimension when we use the verilog 2001 standard.

To do this, we simply add another field which defines the number of elements we need.

The code snippet below shows the general syntax we would use to create a 2D array in verilog.

// General syntax to declare an array type
<type> <size> <variable_name> <elements> <elements>;

As an example, let's consider the case where we want to modify the size of the array from our previous example.

We now want to create a variable which can store 2 elements both of which have 8 4 bit reg type elements.

To do this, we simply add an extra field to the end of our declaration. The code snippet below shows how we would do this.

reg [3:0] example2d [7:0][1:0];

We also use the same method to assign a multidimensional array as we would for a 1D array. However, we now use a pair of square brackets to define the element in both dimensions of the array.

As an example, suppose we want to assign the value of 0xa to the the last element in both dimensions. The verilog code below shows how we would assign data to this element in our array.

example2d [7][1] = 4'ha; 
example2d [7][0] = 4'ha;

We can also simulate this example on EDA playground.

Exercises

Which types of data can we represent in our verilog design?

Binary, hexidecimal, octal and decimal. We can also represent decimal numbers but this is not synthesizable.

What are the two main data types in verilog? What is the difference between them?

Net types are used to model connections in our design and can’t store values. Variable types can store data values and behave like variables in other programming languages.

Which type do we most commonly use to model point to point connections in verilog?

The wire type

Which type do we most commonly use to model the behaviour of storage elements like flip flops?

The reg type.

Name the two different types of numeric types. What are the differences between them?

The integer type represents whole numerical values. The real type can be used to represent decimal values as well.

Write the code to declare an 8 bit wire type and assign it the value of AAh.

// Using big endian convention
wire [7:0] example = 8'haa;
// Using little endian convention
wire [0:7] example = 8'haa;

Declare an array of 16 bit reg types. The array should have a total of 4 elements. Assign the value of FFFFh to the first element in the array and AAAAh to the fourth element in the array.

// Variable declaration
reg[15:0] example [3:0];
// Assigning data to the first element
example[0] = 16'hffff;
// Assigning data to the fourth element
example[3] = 16'haaaa;

How to Write a Basic Verilog Module

This post is the first in a series which introduces the concepts and use of verilog for FPGA design. We start with a discussion of the way Verilog designs are structured using the module keyword and how this relates to the hardware being described. This includes a discussion of parameters, ports and instantiation as well as a full example.

We don’t need to discuss the entire history of verilog in order to work with it. However, we must consider one important historical point – the version of the language we use.

The major standards of verilog were released in 1995 and 2001. In addition to this, another minor update was released in 2005.

We should use the verilog 2001 or 2005 standard for any new FPGA designs which we create.

However, we may still encounter legacy designs which are based on the verilog 1995 standard.

Therefore, we will look at the important differences between the 1995 and 2001 standards as we encounter them in these posts.

Structuring Verilog Code

When we design FPGAs, it is important to remember one fundamental principle – we are designing hardware and not writing a computer program.

Therefore, we must describe the behaviour of a number of different components which we then connect together. This expectation is reflected in the way verilog files are structured.

As with every electronic component, we need to know the external interface to our component. This information allows us to connect it to other components in our system.

We also need to know how the component behaves so that we can use it in our system.

In verilog, we use a construct called a module to define this information. The verilog module is equivalent to the entity architecture pair in VHDL.

The code snippet below shows the general syntax for the declaration of a module in verilog.

module (
  // We can optionally declare parameters here
  parameter <parameter_name> = <default_value>
)
<module_name> (
  // All IO are defined here
  <direction> <data_type> <size> <port_name>
);

 // Functional RTL (or structural) code

endmodule

In this construct, <module_name> would be the name of the module which is being designed. Although we can declare a number of modules in a single file, it is good practise to have one file corresponding to one module.

It is also good practise to keep the name of the file and the module the same. This makes it simpler to manage large designs with many components. 

In verilog we use the // characters to denote that we are writing a comment.

We use comments to include important information about our code which others may find useful. The verilog compiler ignores any thing which we write in our comments.

In the verilog code snippet above, we can see this in practise as comments are used to describe the functionality of the code.

Verilog 1995 Modules

The verilog module declaration syntax was updated as part of the verilog 2001 standard. This means that the method used to declare modules in verilog is slightly different.

When using verilog 1995 code, we only define the name of our ports in the initial module declaration.

We then define the the direction, data type and size of each port in the body of the module. In addition to this, we also define the module parameters in the module body.

The code snippet below shows how we declare modules using verilog 1995 syntax.

module <module_name> (
  // All IO names are defined here
  <port_name>
);

  // We can optionally declare parameters here
  parameter <parameter_name> = <default_value>;

  // We complete the definition of the module IO here
  <direction> <data_type> <size> <port_name>;

  // Functional RTL (or structural) code

endmodule

Parameters in Verilog Modules

Parameters are a local form of constant which we can use to configure a module in verilog.

When we instantiate our module in another part of our design, we can assign the parameters a value to configure the behavior of the module.

As parameters have a limited scope, we can call the same module multiple times and assign different values to the parameters each time.

Therefore, parameters allow us to modify the behaviour of our module on the go.

Parameters are an optional part of the verilog module declaration and in the majority of cases we won't need to include them.

However, parameters allow us to write more generic module interfaces which are easier to reuse in other verilog designs.

After we have declared a parameter in our module, we can use it in the same way as a normal variable.

However, we must remember that it is a constant value so we can only read it. As a result of this, we can only assign a value to the parameter when it is declared.

We discuss verilog parameters in more depth in a later post.

Functional Code

We use the space underneath the module IO declaration to define how our module functions.

We most commonly use RTL for this but we can also write structural code or describe primitives.

These topics are discussed in more detail in later verilog tutorials.

When we have finished writing the code which describes the behaviour of our module, we use the endmodule keyword.

Any code which we write after this keyword will not be included in our module.

Verilog Module Ports

We use ports within the module declaration to define the inputs and output of a verilog module.

We can think of these as being equivalent to pins in a traditional electronic component.

The code snippet below shows the general syntax we use to declare ports.

<direction> <data_type> <size> <port_name>

The <port_name> field in the module declaration is used to give a unique name to each of our ports.

We can define ports as either input, output or inout in our verilog module.

This correspond to inputs, outputs and bidirectional ports respectively. 

The <direction> field in the above construct can be used to do this.

We use the <data_type> field to declare the type of data the port expects.

The most common types are reg and wire which are discussed in the next section.

We talk about the verilog data types in more detail in the next post.

We may also wish to use multi bit, vector type ports in our module. If this is the case we can also declare the number of bits within the port using the <size> field.

When we define the size of an vector type input, we must indicate the most significant and least significant bit (MSB and LSB) in the vector. Therefore, we use the construct [MSB:LSB] when declaring the size of a port.

The example below shows the declaration of an 8 bit input called example_in.

input wire [7:0] example_in;

In this example, the [7:0] size field means that bit 7 is the most significant bit. This is known as little-endian data and is the most commonly used convention in FPGA design.

We could also define the MSB as being in position 0 if we declare the size field as [0:7]. This convention, which is known as big-endian data, is not used as frequently as little-endian when designing FPGAs.

Reg and Wire Types in Verilog

As it is such a large topic, Verilog data types are discussed in more detail in the next blog post.

However, we will quickly look at the two most commonly used types in verilog module declarations - reg and wire.

We use the wire type to declare signals which are simple point to point connections in our verilog code.

As a result of this, wires can't drive data and do not store values.

As the name suggests, they are roughly equivalent to a wire in a traditional circuit.

We use the reg type to declare a signal which actively drives data in our verilog code. As the name suggests, they are roughly equivalent to a flip flop in a traditional digital circuit.

As the wire type is a basic point to point connection, we can use wires as either input or output types when we declare a verilog module.

In contrast, we can only use the reg type for outputs in a verilog module.

We primarily use the wire type to model combinational logic circuits in verilog.

When we use the assign keyword to model combinational logic in verilog we can only use it with a wire type. The assign keyword is discussed in more detail in a future post.

We primarily use the reg type to model sequential logic circuits in verilog.

As we discuss in a future blog post, we must use the always block to model sequential logic circuit. We can only use the reg type inside of an always block.

When we declare a module port, the data type will be a wire by default. As a result of this, we can omit the <data_type> field when we use a wire type port.

Verilog Module Instantiation

We can invoke a verilog module which we have already written in another part of our design. This process of invoking modules in verilog is known as instantiation.

Each time we instantiate a module, we create a unique object which has its own name, parameters and IO connections.

In a verilog design, we refer to every instantiated module as an instance of the module. We use instantiation to create a number of different instances which we use to build a more complex design.

We can think of module instantiation in verilog as being equivalent to placing a component in a traditional electronic circuit.

Once we have created all of the instances we require in our design, we must interconnect them to create a complete system. This is exactly the same as wiring components together in a traditional electronic system.

Verilog provides us with two methods we can use for module instantiation - named instantiation and positional instantiation.

When we write code using the verilog 2001 standard, we can use either positional or named association to connect the ports in our module.

However, we must use positional association to assign values to any parameters in our module when writing verilog 1995 compatible code.

Positional Module Instantiation

When using the positional instantiation approach in verilog, we use an ordered list to connect the module ports. The order of the list we use must match the order in which the ports were declared in our module.

As an example, if we declare the clock first, followed by a reset then we must connect the clock signal to the module IO first.

The verilog code snippet below shows the general syntax for positional module instantiation.

<module_name> # (
  // If the module uses parameters they are connected here
  <parameter_value>
)
<instance_name> (
  // Connection to the module ports
  <signal_name>, // this connects to the first port
  <signal_name>  // this connects to the second port
);

The <module_name> field must match the name we gave the module when we declared it.

We use the <instance_name> field to give a unique name to an instantiated module in our design.

This method can be difficult to maintain as the order of our ports may change as our design evolves.

Positional Instantiation Example

Let's consider a basic practical example to show how we use positional instantiation in practise.

For this example, we will create an instance of the simple circuit shown below.

A logic circuit with the function (a and b) or c.

When we use positional instantiation, the order of the ports in the module declaration is important. The code snippet below shows how we would declare a module for this circuit.

and_or (
  input a,
  input b,
  input c,
  output logic_out
);

Finally, the verilog code snippet below shows how we would create an instance of this module using positional instantiation.

// Example using positional instantiation
and_or example_and_or (
 in_a,
 in_b, 
 in_c, 
 and_or_out
);

Named Module Instantiation

When we use named module instantiation in verilog, we explicitly define the name of the port we are connecting our signal to. Unlike positional instantiation, the order in which we declare the ports is not important.

This method is generally preferable to positional instantiation as it produces code which is easier to read and understand.

It is also easier to maintain as we can modify ports without having to worry about the order in which we declare them.

The verilog code snippet below shows the general syntax for named module instantiation.

<module_name> # (
  // If the module uses parameters they are connected here
  .<parameter_name> (<parameter_value>)
)
<instance_name> (
  // Connection to the module ports
  .<port_name> (<signal_name>), 
  .<port_name> (signal_name>)
);

The <module_name>, <parameter_name> and <port_name> fields must match the names we used when defining the module.

The <instance_name> has the same function for both positional and named instantiations.

Named Instantiation Example

Let's consider a basic practical example to show how we use named instantiation in practise.

For this example, we will create an instance of the simple circuit shown below. This is the same circuit we previously used in the positional instantiation example.

A logic circuit with the function (a and b) or c.

The verilog code snippet below shows how we would create an instance of this module using named instantiation.

// Example using positional instantiation
and_or example_and_or (
 .a (in_a),
 .b (in_b), 
 .c (in_c), 
 .logic_out (and_or_out)
);

Verilog Module Example

In order to fully understand all of the concepts which we have discussed in this post, let's look at a basic example.

In this example, we will create a synchronous counter circuit which uses a parameter and then instantiate two instances of it.

One of these instantiations will have 12-bits in the output whilst the other will have only 8 bits.

We will exclude the RTL for these modules here as we have not yet learnt how to write this. Instead we will simply define the IO of our modules and the interconnection between them.

The counter module will have two inputs - clock and reset - and a single output - the counter value.

In addition to this, we will also need a single parameter which we will use to define the number of bits in the output.

The code snippet below shows the declaration of our counter module using both verilog 2001 and verilog 1995 compatible code.

// Verilog 2001 module declaration
module counter #(
  parameter WIDTH = 8
)
(
  input clock,
  input reset,
  output reg [WIDTH-1:0] count
);

// Verilog 1995 module declaration
module counter (
  clock,
  reset,
  reg
);

paramater WIDTH = 8;

input clock;
input reset;
output reg [WIDTH-1:0] count

endmodule

For the rest of this example we will only use the verilog 2001 standard.

We now need a module which we can use to instantiate two instances of this counter. This module will have two inputs - clock and reset - and two outputs coming from the instantiated counters.

In the counter module, we defined the default counter output as 8 bits. This means that we can instantiate the 8 bit counter without overriding the parameter value.

However, when we instantiate the 12 bit counter, we must also override the value of the WIDTH parameter and set it to 12.

The code snippet below shows the code for this module when using named instantiation to connect to the ports.

module top_level (
  input clock,
  input reset,
  output reg [7:0] count_8,
  output reg [11:0] count_12
);

  // Instantiation of the 8 bit counter
  // In this instance we can use the default
  // value fo the parameter
  counter 8bit_count (
    .clock (clock),
    .reset (reset),
    .count (count_8)
  );

  // Instantiation of the 12 bit counter
  // In this instance we must override the 
  // value fo the WIDTH parameter
  counter #(.WIDTH (12)) 12_bit_count (
    .clock (clock),
    .reset (reset),
    .count (count_12)
  );

endmodule

Exercises

What do we use a module for in verilog?

We use modules to define the behavior of a component in verilog.

What do we use parameters for in a verilog module?

We can use parameters to configure the behaviour of our module when we instantiate it.

List the three different types of direction a port can have.

Inputs (input keyword), outputs (output keyword) and bidirectional (inout keyword).

What is the main difference between the reg and the wire type?

The reg type can drive data and store values whereas the wire type can’t.

What is the difference between named and positional instantiation? Which one is easier to maintain and why?

We use an ordered list to connect ports when using positional instantiation. We have to explicitly define the port we are connecting to when we use named instantiation. Named instantiation is easier to maintain as the code is not affected if we change the order of the ports in the module declaration.

Write a module declaration for the circuit shown below using both verilog 1995 and verilog 2001.

A circuit diagram showing a two input multiplexor and a d type flip flop. The output of the multiplexor is the input to the flip flop.
// Verilog 2001 style module declaration
module mux_ff (
 input clock,
 input a,
 input b,
 input addr,
 output reg q
);

// Verilog 1995 style module declaration
module mux_ff (
 clock,
 a,
 b,
 addr,
 q
);

input clock;
input a;
input b;
input addr;
output reg q;

endmodule

Sign up free for exclusive content.

Don't Miss Out

We are about to launch exclusive video content. Sign up to hear about it first.

Close
The fpgatutorial.com site logo

Don't Miss Out

We are about to launch exclusive video content. Sign up to hear about it first.

Close