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.
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.
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 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.
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.
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.
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.
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.
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.
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 and_or example_and_or ( in_a, in_b, in_c, and_or_out );
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.
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 and_or example_and_or ( .a (in_a), .b (in_b), .c (in_c), .logic_out (and_or_out) );
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
What do we use a module for in verilog?show answer
We use modules to define the behavior 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 in the module declaration.hide answer
Write a module declaration for the circuit shown below using both verilog 1995 and verilog 2001.show answer
// 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
Leave a Reply