Unit 3: Introduction to Verilog for FPGAs
Traditionally schematic capture was the means to create a digital design. However as designs became larger and more complex this design method became difficult to debug, revise, edit, and maintain. These are the reasons why the Electronic Design Automation (EDA) vendors started to introduce design tools based on hardware description languages (HDLs). Although HDLs are the main method to create the distinct design blocks of a system it is still common to see schematics used to represent the higher levels of a design to connect the lower level design blocks.
There are two main Hardware Design Languages (HDLs) used today, Veriliog and VHDL. People will no knowledge of either language should find Verilog the easiest of the two to grasp. Also those people with previous experience in the 'C' programming language should find some useful similarities to Verilog. For these reasons Verilog will be the language of choice to demonstrate digital design methods in this module.
It is useful to stress here that HDLs are not programming languages but are as the name describes a hardware description language. That is HDL 'code' describes the the functionality of a circuit.
Verilog was originally designed as a digital simulation language and many of the constructs do not synthesise. That is they cannot be used to produce a netlist. Only the synthesisable elements of the language will be introduced here.
Unit Contents
3.1 Introduction
This section introduces some terminology used in HDLs.
3.1.1 Abstraction Level
There are three levels of abstraction that can be used to describe digital designs.
- Gate Level
- Data Flow
- Behavioural or Algorithmic Level
N.B. These can be compared to machine code, assembly, and 'C' when programming a micro-controller.
Gate level of abstraction describes the circuit purely in terms of gates. This approach works well for simple circuits where the number of gates is small because the designer can instantiate and connect each gate individually. Also this level is very intuitive to the designer because of the direct correspondence between the circuit diagram and the Verilog description.
If the design is very large then a higher level of abstraction should be used above gate level. Data Flow allows the designer to design a circuit in terms of the data flow between registers and how a design processes data rather than the implementation of individual gates. The design tools would then convert the description to a gate level circuit.
This level of abstraction allows designers to describe the circuit's functionality in an algorithmic way. That is the designer describes the behaviour of the circuit without any consideration to how the circuit transfers to hardware.
Note that the term RTL (Register Transfer Level) is commonly used for a combination of Data Flow and Behavioural modeling.
3.1.2 Types of Coding
Verilog HDL allows circuits to be described in two ways, structuraly (how they are built), or in terms of behaviour, (what they do). It is important to remember that when you desribe your circuits behaviour rather than its structure then the systhesis tool has to translate the description into logic. The synthesis tool although very good at its job may not always translate the description into the logic you intended, and also may not produce the most efficient circuit possible. For these reasons it is useful to have an idea of how your description will be translated into logic. Unit 5 covers some typical Verilog circuits and how they translate into hardware.
The two types of coding styles in Verilog:
- Structural or Continuous - used for primitive descriptions or data flow without storage
and(q, a, b); //structural assignment using Verilog primitive gate
assign q = a & b | c; //continuous assignment
|
- Behavioural or Procedural - can be used to describe circuits with storage, and / or combinational logic. This type of coding will be covered in more detail later.
always@(posedge clock)
begin
count <= count + 1;
if(a)
z <= b;
end
|
3.2 Module Components
The main component of a Verilog design block is a module. A simple module construction is shown below and illustrates the three levels of abstraction:
module my_new_module(a, b, z);
input a, b;
output z;
wire d, q;
reg z;
//Gate Level
and(q, a, b);
//Data Flow
assign d = a & b;
//Behavioural
reg z;
always@(*)
begin
if(a)
z <= b;
end
endmodule
|
3.2.1 Interface Specification
module my_new_module(clock, a, b, z);
input clock, a, b;
output z;
wire d, q;
reg z;
//Gate Level
and(q, a, b);
//Data Flow
assign d = a & b;
//Behavioural
reg z;
always@(posedge clock)
begin
if(a)
z <= b;
end
endmodule
|
The interface section requires two keywords module and endmodule as highlighted above. The name of the module follows the module statement. All the interface signals (ports) to and from the module follow the module name and are enclosed in parenthesis. If this is the top-level module then these signals will correspond to the relevant FPGA device pins, otherwise they will be internal signals to the design module and will interface to a higher level module.
3.2.2 Declaration
module my_new_module(clock, a, b, z);
input clock, a, b;
output z;
wire d, q;
reg z;
//Gate Level
and(q, a, b);
//Data Flow
assign d = a & b;
//Behavioural
reg z;
always@(posedge clock)
begin
if(a)
z <= b;
end
endmodule
|
The declaration section of the module specifies the port directions, and any internal signals. Port directions can be input, output, or inout. Also any internal declarations are made here such as variables, wires, and nets.
3.2.3 Implementation
module my_new_module(clock, a, b, z);
input clock, a, b;
output z;
wire d, q;
reg z;
//Gate Level
and(q, a, b);
//Data Flow
assign d = a & b;
//Behavioural
reg z;
always@(posedge clock)
begin
if(a)
z <= b;
end
endmodule
|
This section of the module describes the functionality of the circuit using either individual or combinations of gate level, data flow, and behavioural modeling.
3.2.4 Comments
Comment are specified in two ways. One uses a double forward slash // and the other a combination of /* and */. The usage of these is shown below:
assign a = b; //this is a single line comment
/* This is a block
comment
that can continue on more than one line */
|
3.3 Hierarchy
Verilog allows more complex modules to be created using simpler modules. This is called instantiation and has several benefits:
- Allows a hierarchical structure to be created and so makes the overall design more understandable and readable
- Allows re-use of modules
- If a bug is found in a lower level module that is used (instantiated) throughout the design then only the one module needs to be modified and this change will be reflected throughout the whole design
- Allows a large complex design to be divided (partitioned) into lower level modules that can then be distributed between a team of designers
- Allows quick individual testing of these simpler lower level modules
Therefore a typical Verilog design consists of one top-level module and one or many lower-level modules. The top-level module contains a list of ports that connect to the outside world (pins on the device) and interconnections to the lower level modules.
Instantiation of a module is illustrated below and shows two methods to create an instance of a separate module:
module Test(in1, in2, in3, in4, sel, out);
input in1, in2, in3, in4, sel;
output out;
wire out1, out2;
//signals reference by order
mux2to1 m1(in1, in2, sel, out1);
//connections by name - order doesn't matter
mux2to1 m2(.q(out2), .a(in4), .select(sel), .b(in3));
assign out = out1 & out2;
endmodule
module mux2to1(a, b, select, q);
input a, b, select;
output q;
reg q;
always@(*)
begin
if(select)
q <= a;
else
q <= b;
end
endmodule
|
The lower level module is called mux2to1 and is instantiated twice in the module Test. The syntax is:
module_name instance_name_1 (port_connection_list);
In the first instance of the mux the port connection list is referenced by order. That is, the order of the signals from the instantiation (m1) must match the order of the module definition (mux2to1).
In the second instance the order of the port connection list doesn't matter because the signals are referenced in the instance. For example .a(in4) connects the 'a' signal in the definition to the 'in4' signal in the instantiation.
The above example compiled and viewed (RTL Viewer) using Altera Quartus II software is shown in Fig 1.
3.4 Built-in Logic Primitives
Verilog includes primitive logic gates as part of it's language. These are described below.
3.4.1 Basic Gates
Basic gates have a single output and any number of inputs. They consist of AND, NAND, OR, NOR, XOR, XNOR
The syntax is:- gate_name instance_name(output, input1, input2,......inputn);
Example:- and and1(out, in1, in2, in3);
3.4.2 Buffers
These primitives implement buffers and invertors. They have one input and any number of outputs. They consist of buf, and not.
The syntax is:- gate_name instance_name(output1, output2,.....outputn, input);
Example:- not not1(out1, out2, in);
3.4.3 Tri-state Buffers
These primitives implement 3-state buffers and invertors. They have one input and any number of outputs. Their output can be logic 1, logic 0, or high impedance depending on the input and the control signals. They consist of bufif0 and notif0 for an active low control signal, and bufif1 and notif1 for an active high control signal.
The syntax is:- gate_name instance_name(output1, output2,.....outputn, input);
Example:- notif1 notif1_1(out, in, control);
if control is low, out is high impedance, if control is high then out is the inverse of in.
3.5 Data Types
This section introduces the various data types available in Verilog.
3.5.1 Value Set
Verilog supports four values:
Value Level | Condition |
0
| Logic zero or False Condition |
1
| Logic 1 or True Condition |
x
| Unknown Logic Value |
z
| High Impedance or Floating State |
3.5.2 Wires
Wires are used as connections between hardware elements and are treated just as wires in real circuits. The wire can be read or assigned to but does not store it's value. It must be driven by a continuous assignment or connected to the output of a gate or module. Other types of wires are wand, wor, and tri.
Wires are one bit values by default but can be declared as vectors as shown below.
wire a; //declare single wire
wire a, c; //declare two wires
wire d = 1'b0; //wire is fixed to logic value 0
wire [7:0] A; //vector of 8 wires
|
Note:
Quartus does not require you to define one bit wires so the code Test1 will be ok and the wire Pulse1s will be defined automatically.
module Test1(CLK, Count);
input CLK;
output [7:0] Count;
CLKDIV clkdiv( CLK, Pulse1s);
COUNTER counter(CLK, Pulse1s, Count);
end module
|
However if we require a multi-bit wire then we MUST declare it as shown in Test2.
module Test2(CLK, Decode);
input CLK;
output [7:0] Decode;
wire [7:0] Count;
CLKDIV clkdiv( CLK, Pulse1s);
COUNTER counter(CLK, Pulse1s, Count);
Decoder decoder(CLK, Count, Decode);
end module
|
3.5.3 Registers
Registers represent data storage elements and retain their value until another value is placed on them. They do not necessarily produce a physical register built from edge triggered flipflops in real circuits. In Verilog it is simply a variable that can hold it's value.
reg a; //declare single 1-bit variable
reg a, c; //declare two 1-bit variables
reg [7:0] A, B; //two 8-bit variables
|
3.5.4 Input, Output, and Inout
These keywords declare the port direction of a module. The input and inout ports are of type wire. An output can be of type wire, wand, wor, tri, or reg. Examples of port declarations are given below.
module port_example(a, b, c, d);
input a;
output b, c;
output d;
reg d; //the output above also declared as a reg
|
3.5.5 Integers
Integers are general purpose variables used for purposes such as counting. It is possible to use reg as a general purpose variable but a reg stores values as unsigned whereas integers store values as signed. If an integer holds numbers that are not defined at compile time their size will default to 32-bits. If they hold constants the synthesiser will adjust them to the minimum width needed at compile time.
3.5.6 Parameters
The keyword parameter allows constants to be defined in a module. Parameters cannot be used as variables. The most simplest use of a parameteris to give a constant value a meaningful description as shown below.
parameter bit_width 8; //meaning full name for a constant
reg [bit_width-1:0] output_bus;
|
Parameters also have a much more powerful and useful feature that is they can be overridden individually at compile time by higher level modules. This is demonstrated in the following example code.
module Test(clk, out1, out2, out3);
input clk;
output out1, out2, out3;
defparam t3.n = 4;
timer t1(clk, out1);
timer #(3) t2(clk, out2);
timer t3(clk, out3);
endmodule
module timer(clkin, clkout);
input clkin;
output clkout;
parameter n = 2;
reg [n:0] count;
always@(posedge clkin)
begin
count <= count + 1'b1;
end
assign clkout = count[n];
endmodule
|
Firstly a timer module timer is declared with a default parameter n set as 2. This causes the output of the module to act as a divide by 4 counter.
In the top-level module Test, three instances of timer are created. The first instance creates a timer with the default setting (divide by 4). The second instance overrides the default setting to 3 ( #(3) ) giving a divide by 8. And the third instance uses the defparam keyword to overwrite the default setting to 4, giving a divide by 16 timer.
3.6 Operators
Operators in Verilog are similar to operators found in other languages.
Operators can be used in both continuous and procedural assignments
3.6.1 Arithmetic
The list of arithmetic operators supported by Verilog are listed below:
- + (addition)
- - (subtraction)
- * (multiplication)
- / (division)
- % (modulus)
3.6.2 Relational
Relational operators compare two operands and return a single bit. These operators synthesise into comparators.
- < (less than)
- <= (less than or equal to)
- > (greater than)
- >= (greater than or equal to)
- == (equal to)
- != (not equal to)
3.6.3 Bit-wise
Bit-wise operators carry out a bit-by-bit comparison of two operands.
- ~ (bit-wise NOT)
- & (bit-wise AND)
- | (bit-wise OR)
- ^ (bit-wise XOR)
- ~^ (bit-wise XNOR)
3.6.4 Logical
Logical operators return a single bit. They can work on expressions, integers, and groups of bits. If any of the bits in the operand are non-zero then it is treated as a logic 1. Logic operators are typically used with if..else statements.
- ! (Logical NOT)
- && (Logical AND)
- || (Logical OR)
3.6.5 Reduction
Reduction operators operate on all the bits of a vector. For instance if we have a vector [2:0] a. Then z = &a returns 1 if all the bits of 'a' are 1.
- & (reduction AND)
- | (reduction OR)
- ~& (reduction NAND)
- ~| (reduction NOR)
- ^ (reduction XOR)
- ~^ (reduction XNOR)
3.6.6 Shift
Shift operators shift the first operand by the number of bits specified in the second operand. Empty positions are filled with zeros.
- << (shift left)
- >> (shift right)
3.6.7 Concatenation
The concatenation operator combines two or more operands to form a larger vector.
{ } (concatenation)
Example if a = 0010 and b = 110010 then z = {a,b} gives z = '0010110010'
3.6.8 Replication
The replication operator makes multiple copies of an item.
{n{item}} (n times replication of item)
Example if a = 0010 then y = {2{a}} gives y = '00100010'
3.6.9 Conditional
The conditional operator returns one of two expressions based on a condition. This operator synthesises to a multiplexer.
(condition) ? result1 if true : result2 if false
Example z = (x) ? a : b returns a if x is true or returns b if x is false
3.7 Operands
3.7.1 Numbers
Unless defined by the designer the size of a Verilog number is 32-bits wide. The format of a Verilog number is size'base value. If no size is specified then the number is assumed to be 32-bit. All numbers are padded to the left with zeros if necessary. The available base formats are, b - binary, o - octal, d - decimal, and h - hexadecimal. Some examples follow.
476 //32 bit decimal
8'd255 //8 bit decimal
1'b0 //single bit binary
4'b0110 //4 bit binary
8'b1100 //8 bit binary 00001100
4'hb //4 bit hex number 1011
'h9c //32 bit hex
|
3.7.2 Bit Selects
Bit selects and part bus selects can be achieved in Verilog. The syntax is:
variable_name [index]
variable name[msb:lsb]
The following code snippet illustrates how this is done.
reg [7:0] a,b; //declare the registers
//single bit select
c = a[6] & b[3]; //AND bit 7 of 'a' with bit 4 of 'b'
//part bit selects
sum = a[7:5] + b[2:0]; //add the 3 msb of 'a' to 3 lsb of 'b'
|
3.8 Compiler Directives
A compiler directive is used to control the compilation of a Verilog file. The grave accent mark, `, (usually to the left of the number 1 key) denotes a compiler directive. A directive is effective from the point at which it is declared to the point at which another directive overrides it, even across file boundaries. Compiler directives may appear anywhere in the source description, but it is recommended that they appear outside a module declaration. Some common directives are introduced below.
`include - This compiler directive lets you insert the entire contents of a source file into another file during Verilog compilation. The compilation proceeds as though the contents of the included source file appear in place of the `include command. You can use the `include compiler directive to include global or commonly-used definitions and tasks, without encapsulating repeated code within module boundaries.
`define - This compiler directive lets you create macros for text substitution and create macros to trigger the `ifdef compiler directive. You can use text macros both inside and outside module definitions
`undef - The `undef compiler directive lets you remove definitions of text macros created by the `define compiler directive. This use the `undef compiler directive to undefine a text macro that you use in more than one file
`ifdef...`else...`endif - Optionally includes lines of source code during compilation. The `ifdef directive checks that a macro has been defined, and if so, compiles the code that follows. If the macro has not been defined, the compiler compiles the code (if any) following the optional `else directive. You can control what code is compiled by choosing whether to define the text macro with `define. The `endif directive marks the end of the conditional code
3.9 Procedural Coding
3.9.1 always Procedural Assignments
Procedural assignments are always made in
always blocks. Only
reg and
integer values can be assigned (placed on the left hand side of the = sign). The right hand side of the assignment is an expression that can use any of the operators described in section
3.6. The
always block can be used to describe both combinational and sequential designs. If there is more than one line of code in the always block then this must be enclosed within
begin....end keywords. These points are illustrated by the two code snippets below:
//combinational design
always @(*)
begin
statement1;
statement2;
end
|
//sequential design
always @(posedge clock)
begin
statement1;
statement2;
end
|
The always statement controls when the circuit described within the always block will be triggered. The terms included in parathesise following thealways& statement is called the sensitivity list. It is these terms that control the triggering of the circuit.
There are two types of sensitivity lists, level and edge. Level sensitivity lists allow us to describe combinational circuits. Edge sensitivity lists allow us to describe flip flop circuits.
if we are describing a combinational circuit then all the inputs to the circuit should be included in the sensitivity list. Consider an AndOrInvert circuit and the corresponding code shown in Fig 3.
As shown, all the circuit inputs, a, b, c, and d, are included in the sensitivity list.
Please note however; Verilog-2001 offers additional syntax for the sensitivity list which is:
always@(*)
This creates an implicit sensitivity list that contains all the signals whose values are read in the statements of the always block.
An edge triggered circuit can either be positive edge of negative edge.
- positive edge trigger - always@(posedge CLK)
- negative edge trigger - always@(negedge CLK)
Note: To ensure a design is fully synchronous do not mix the two edge triggered options.
Figure 4 shows a circuit using a flipflop and the corresponding code.
3.9.2 Blocking and Non-blocking Assignments
Two types of assignments can be made in a procedural block, blocking (=) and non-blocking (<=). With blocking assignments each one is 'processed' sequentially in the order they are written. with non-blocking assignments the operations are carried out in parallel. The two types of assignment are illustrated below. Fig 3 and 4 show the circuits created when changing the order of the blocking statements. Fig 5 and 6 show how changing the order of non-blocking makes no difference to the synthesised circuit.
3.9.3 begin...end
As previously mentioned begin....end statements are used to group several statements together in places such as always blocks and if, case, andfor statements. If only one statement follows these statements then the begin...end can be omitted.
3.9.4 for Loops
This is the same as a for loop in C, where it is used to repeatedly execute a statement/s. Do not forget however that this is not a processor and the statements are not sequentially executed on the hardware, it is a hardware description of a circuit. It is a neat concise method of describing your circuit. The following example code snippet and generated hardware circuit shown in Fig 7 should re-enforce this.
3.9.5 if...else if...else
The if...else statements execute a statement or a block of statements following the if depending on the result of an expression. The if can be used on its own, with an else or using an else if with a second expression. The syntax of these three methods are shown below:
//a pure if expression
if(expression)
begin
statement1
statement2
end
//if...else if expression
if(expression1)
begin
statement1
statement2
end
else if(expresssion2)
begin
statement3
statement4
end
//if...else expression
if(expression)
begin
statement1
statement2
end
else
begin
statement3
statement4
end
|
3.9.6 case Statements
The case statement allows a multi-path branch to be executed depending on the expression and the list of case choices. A default alternative can be included that will be executed if none of the other alternatives are matched. The syntax and code snippet is illustrated below:
syntax:
case (expression)
alternative1: statement1;
alternative2: statement2;
alternative3: statement3;
default: default_statement;
endcase
code snippet:
case(alu_control)
2'b00: y = a+b;
2'b01: y = a-d;
2'b10: y = a&b;
default: y = 0;
endcase
|
Updated 29/07/2008 KS