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.
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 : <module_name>
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.
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 the design, we can assign the parameters a value to configure the behavior of the module.
As parameters are only visible locally, 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 a lot of the time 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.
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 laters posts.
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 <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 if 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 a 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.
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 it was declared. 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.
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 example_and_or 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.
The verilog code snippet below shows how we would create an instance of this module using named instantiation.
// Example using positional instantiation example_and_or 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 require a single parameter which we will use to define the number of bits in the output.
The verilog code snippet below shows the declaration of our counter module.
module counter #( parameter WIDTH = 8 ) ( input clock, input reset, output reg [WIDTH-1:0] count );
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) ) ( .clock (clock), .reset (reset), .count (count_12) ); endmodule : top_level
What do we use a module for in verilog?show answer
We use modules to define the behaviour of a component in verilog.hide answer
What do we use parameters for in a verilog module?show answer
We can use parameters to configure the behaviour of our module when we instantiate it.hide answer
List the three different types of direction a port can have.show answer
Inputs (input keyword), outputs (output keyword) and bidirectional (inout keyword).hide answer
What is the main difference between the reg and the wire type?show answer
The reg type can drive data and store values whereas the wire type can’t.hide answer
What is the difference between named and positional instantiation? Which one is easier to maintain and why?show answer
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 int he module declaration.hide answer