Logic Design - Verilog Introduction


[Edit of Image1]

Introduction

Hey it's a me again @drifter1!

Today marks the beginning of a new series on Logic Design where we will be covering the Hardware Description Language (HDL) known as Verilog. There already is such a series for VHDL on this blog, but Verilog and more specifically its "extension" SystemVerilog are gaining more and more popularity over VHDL. Let's also note that for someone who knows VHDL already the jump to Verilog should not be too confusing, because there are lots of similarities between the two. Its hardware development after all.

What will be covered in this series is basically the synthesizable subset of Verilog 2001, which is quite common. After this series is over there might also be a small series on SystemVerilog, which adds more hardware constructs and generally even more functionality. And when that is out of the way, Berkeley's Chisel extension of Scala or SpinalHDL might also be interesting to cover, as they can be used as generators of VHDL/Verilog that speed-up the whole RTL design process even more. But, of course knowing VHDL/Verilog is still necessary, but such tools are also gaining popularity, maybe because of the RISC-V ISA (which is also a part of my master's thesis).

Last, but not least, I think I also have to mention that it might finally be time to get into Computer Architecture again, by starting with the implementation of a complete CPU in Verilog/SystemVerilog. I also haven't forgotten about my compiler series, but this whole machine-code (assembly) generation stuff is quite complicated, and switching to the instruction set of the "custom" CPU is also on my mind. Of course a major reconstruction of the GitHub repository is a must, so that it's easier for newer readers. But, we shall see...

Anyway, now that that's out of the way, let's start with a small introduction of Verilog!


Basic Syntax

First of all, Verilog is a case-sensitive language, which means that the identifiers used for "variables" have to be chosen with caution. For example, sig is not the same as Sig. The identifiers have to start with an alphabetic character or underscore (_), but can also contain numbers or a dollar sign ($) afterwards. As such, sig_1 is a valid identifier. In addition to these naming rules, the various keywords that Verilog uses, such as module, wire or reg are also forbidden to be used as identifiers (keywords in Verilog are lower case). Similar to many programming languages, white space (blank spaces, tabs, new lines etc.) is mostly ignored, if its not used for seperating the lexical tokens of the "program".

Comments can be specified in a C-like way using:

  • // for single-line comments
  • /* and */ for multi-line comments

Each assignment, definition or declaration in Verilog ends with a semicolon (;). This allows the RTL programmer to use more lines in order to improve code readability.


Data Types

Verilog's signals, constants, variables and functions have to be assigned to a specific data type. Some of these data types are synthesizable, whilst others are only meant to be used for behavioral modeling and simulation, and therefore should be avoided. The data types can be divided into:

  • Value data types
  • Net data types - connect components
  • Variable data types - store values

Value data types

The four basic "logic" values of Verilog are:

  • 0 - logical zero or false
  • 1 - logical one or true
  • X or x - unknown or uninitialized logical value
  • Z or z - high impedance or tri-state gate
The x value can be used as a debug tool in order to identify missing initializations, bad implementations in general, missing connections etc.

Net data types

A net data type is used for the purpose of modeling the physical connection between two structural elements of a logical circuit. The values that such a signal can take are the logic values discussed previously (0, 1, x, z), and is dependent on the value of their drivers. The main data type of this category is wire, which is also synthesizable and used in basically all hardware designs.

For example:

wire sig_1;
specifies a signal that can be used for the purpose of connecting an 1-bit output of a module with the 1-bit input of another module.

Variable data types

For storing values, the two main synthesizable data types that are used in RTL design are:

  • reg - for storing logical values (unsigned variable)
  • integer - for storing integers (signed variable)
For example:
reg sig_2;
specifies a 1-bit register.

The data type real, which used is for storing floating-point numbers is not synthesizable and should be avoided in the hardware description part of a design. And let's also not forget to mention the data type time, which is an unsigned integer and useful for simulation purposes.

Vectors

Of course, the wires and registers which are used in a design don't have to be of 1-bit length. Verilog provides a means of defining vectors using the net and variable data types mentioned earlier. Vectors are defined as follows:

<type> [MSB_index : LSB_index] identifier

For example,

wire [3 : 0] w;
reg [31 : 0] r;
specify a 4-bit vector of the type wire with identifier "w", and a 32-bit register of type reg with identifier "r".

Using the identifier of the vector its possible to "extract" one ore more bits using id[index] and id[msb : lsb] correspondingly. For example, the bit '0' of register "r" can be extracted using r[0] and a 2-bit vector of wire "w" which extends from bit 2 to 1 can be taken using w[1 : 0].

Arrays

Multi-dimensional arrays are also easy to define, and can be thought of as "vectors of vectors". All of these vectors of course have the same dimensions. Arrays can be defined as follows:

<type> [MSB_index : LSB_index] id [array_start : array_end]

For example, an array (basically memory) of 32 8-bit elements can be defined using the following one-liner:

reg [7 : 0] mem [0 : 31]

Accesing the elements of such a data type is similar to vectors. The main difference is that an additional index has to be specified. In order to access the 3rd element of the array "mem" (or vector at index 2), which is basically the complete 8-bit register, is done using mem[2]. The most-significant bit (MSB) of this same vector, and so the bit at index 7, can be taken using mem[2][7].


Number Format

When no additional information is specified, any number in Verilog is treated as an decimal. But, its possible to define the exact base, such as binary, octal and hexadecimal. The syntax used in Verilog is a little more complicated than in VHDL, and number formatting is done using:

<bit_size>'<base><value>
The base can be one of the following:

Let's also note that Verilog is weakly-typed, allowing interoperability between the various data types. Thus, Verilog will remove bits that can't be stored or insert the correct number of 0s in front in order for assignments and operations to succeed. Underscore characters (_) can be used for improving the readability of long bit-strings. Its also possible to leave the number of bits unspecified (unsized). In that case the simulator or compiler will either use a default value or the corresponding size for each assignment. For negative numbers a minus sign (-) has to be put before the size of the number and not between the base format and number.

For example,

8'b1010_0101
specifies the 8-bit unsigned binary number "10100101" which is identical to the unsigned decimal 165.

The following is a great example of the weakly-typed nature of Verilog:

5'32
This line specifies an unsigned decimal that has to fit into 5-bits, but actually needs at least 6-bits, leading to the unsigned binary "00000" as the MSB '1' at index 5 can't be stored using this number format.

An unsized hexadecimal number is specified as follows:

'hcafe


Operators

Verilog contains the usual arithmetic, bit-wise, relational and logical operators, but also more advanced operators such as bit-reduction, bit-concatenation, bit-replication, shifting and the C-like conditional operator (? :). Let's get more in-depth...

Arithmetic operators

The usual addition (+) and subtraction (-) operators are synthesizable. Operators such as multiply (*), divide (/), modulus (%) are also provided, but lead to "bad" hardware and should thus be avoided.

Bit-wise operators

The usual bit-wise AND (&), OR (|), XOR (^) and NOT (~) can be used for bit-wise operations. In the case of two operands, if one is shorter than the other, the shorter one will be extended with left-side zeros to match the length of the longer operand.

Equality and Relational operators

The usual equality (==), inequality (!=) and less than (<), greater than (>), less equal (<=), greater equal (>=) operators are also provided.

Logical operators

Logical operators are also provided in the form of logic negation (!), logical AND (&&) and logical OR (||) which result in a 1-bit, true-false result.

Bit-Reduction operators

Verilog also provides bit-reduction, where the bit-wise operators become unary operators that produce 1-bit result. For example & 4'b1001 is a bit-wise AND reduction that leads to a result of '0', whilst | 3'b010 is a bit-wise OR reduction that leads to a result of '1', because at least one of the bits is non-zero.

Bit-Concatenation operator

Using the brace characters ({, }) with commas separating the expressions within, its possible to specify concatenations. For example {2'b01, 2'b11} is the same as 4'b0111.

Bit-Replication operator

In some cases a group of bits might have to be replicated n times. This is easily done in Verilog using the replication operator:

{n{m}}
For example, {5{2'b01}}} is the same as 10'b0101_0101_01. In other words the number "m" (in this case 2'b01) is replicated n = 5 times.

Shift operators

Verilog also provides left shift (<<) and right shift (>>) operators. But, some synthesis tools may only be able to shift by a constant amount, which doesn't involve any logic.

Conditional operator

Last, but not least, Verilog also provides the C-like conditional operator, which is defined as:

cond_expr ? true_expr : false_expr
Depending on how the conditional expression "cond_expr" evaluates, the result will be either "true_expr" or "false_expr". Its common to nest such conditional operators in order to define combinational logic.


Modules

The main building block of Verilog is the module, which is used to specify the hardware logic and thus implements the functionality of a circuit. A module is enclosed within the module and endmodule keywords. Each module is given a name, and multiple modules with different names can be defined in the same file and in any order. An optional list of ports (the input and output of a given circuit) can be given after the module keyword inside of parentheses. But, ports can also be declared in the main part of the module, where variable declarations, statements etc. are defined.

The basic syntax of a module is as follows:

module <name> [(opt_port_list)];
    /* main contents of module */
endmodule
A module with empty port list is defined as follows:
module <name>;
    /* main contents of module */
endmodule

Ports

As already mentioned earlier, the ports of a given design are the inputs and outputs. By default, ports are of type wire. Input ports are defined using the keyword input, output ports are defined using the keyword output, and ports which are both are defined using the keyword inout.

When the module is defined using an empty port list, the ports are declared inside of the main contents of the module with semicolon-terminated statements of the form:

port_type <net_type> [range] list_of_names;
For example input [7 : 0] a, b; specifies two 8-bit input ports with identifiers "a" and "b" correspondingly, which are of data type wire.

When declared inside of the port list, the port declarations are separated by comma (,). Thus, a module of name "my_module" with two input ports, a and b, and one output port, c, might be defined as follows:

module my_module (output c, input a, b);
    /* main module */
endmodule

Its common to specify the output first, and when the port-list is large enough, the port definitions might also be separated into different lines:

module my_module (  output [15 : 0] c,
                    input [7 : 0] a,
                    input [3 : 0] b );
    /* main module */
endmodule


RESOURCES:

References

  1. http://www.asic-world.com/verilog/veritut.html
  2. https://www.chipverify.com/verilog/verilog-tutorial
  3. https://www.javatpoint.com/verilog

Images

  1. https://www.flickr.com/photos/creative_stock/5227842611

Previous articles of the series

This is the first article of the series, but you can check-out the VHDL series in this quite old recap post.


Final words | Next up

And this is actually it for today's post!

Next time we will cover how to define Combinational Logic using Verilog...

See Ya!

Keep on drifting!

H2
H3
H4
3 columns
2 columns
1 column
1 Comment
Ecency