Lesson 1. Verilog HDL
Lesson 1. Verilog HDL
Verilog was developed to simplify the process and make the HDL more
robust and flexible. Today, Verilog is the most popular HDL used and
practiced throughout the semiconductor industry.
o Behavioral level
o Register-transfer level
o Gate level
Behavioral level
Register-Transfer Level
Gate Level
The characteristics of a system are described by logical links and their timing
properties within the logical level. All signals are discrete signals. They can
only have definite logical values (`0', `1', `X', `Z`).
The usable operations are predefined logic primitives (basic gates). Gate
level modeling may not be the right idea for logic design. Gate level code is
generated using tools such as synthesis tools, and his netlist is used for gate-
level simulation and backend.
History of Verilog
o Verilog HDL's history goes back to the 1980s when a company called
Gateway Design Automation developed a logic simulator, Verilog-XL,
and a hardware description language.
o Cadence Design Systems acquired Gateway in 1989 and with it the
rights to the language and the simulator. In 1990, Cadence put the
language into the public domain, with the intention that it should
become a standard, non-proprietary language.
o The Verilog HDL is now maintained by a nonprofit making organization,
Accellera, formed from the merger of Open Verilog International (OVI)
and VHDL International. OVI had the task of taking the language
through the IEEE standardization procedure.
o In December 1995, Verilog HDL became IEEE Std. 1364-1995. A
significantly revised version was published in 2001: IEEE Std. 1364-
2001. There was a further revision in 2005, but this only added a few
minor changes.
o Accellera has also developed a new standard, SystemVerilog, which
extends Verilog.
o SystemVerilog became an IEEE standard (1800-2005) in 2005.
Verilog creates a level of abstraction that helps hide away the details of its
implementation and technology.
For example, a D flip-flop design would require the knowledge of how the
transistors need to be arranged to achieve a positive-edge triggered FF and
what the rise, fall, and CLK-Q times required to latch the value onto a flop
among much other technology-oriented details.
Power dissipation, timing, and the ability to drive nets and other flops would
also require a more thorough understanding of a transistor's physical
characteristics.
Lexical Tokens
A lexical token may consist of one or more characters, and every single
character is in exactly one token.
White Space
White space can contain the characters for tabs, blanks, newlines, and form
feeds. These characters are ignored except when they serve to separate
other tokens. However, blanks and tabs are significant in strings.
Comments
1. Single line comments begin with the token // and end with a carriage
return.
For example, //this is the single-line syntax.
2. Multi-Line comments begin with the token /* and end with the token */
For example, /* this is multiline syntax*/
Numbers
1. Integer Number
Verilog
Syntax
1. <size>'<radix><value>
2. Real Numbers
erilog supports both the type of numbers, but with certain restrictions. In C
language
, we don't have int and unint types to say if a number is signed integer or unsigned integer.
Any number that does not have a negative sign prefix is positive. Or indirect
way would be "Unsigned".
Negative numbers can be specified by putting a minus sign before the size
for a constant number, thus become signed numbers. Verilog internally
represents negative numbers in 2's complement format. An optional signed
specifier can be added for signed arithmetic.
4. Negative Numbers
Negative numbers are specified by placing a minus (-) sign before the size of
a number. It is illegal to have a minus sign between base_format and
number.
Identifiers
The identifier is the name used to define the object, such as a function,
module, or register. Identifiers should begin with alphabetical characters or
underscore characters.
1. Escaped Identifiers
Operators
1. Arithmetic Operators
2. Relational Operators
These operators compare two operands and return the result in a single bit,
1 or 0. The Operators included in relational operation are:
o == (equal to)
o != (not equal to)
o (greater than)
o >= (greater than or equal to)
o < (less than)
o <= (less than or equal to)
3. Bit-wise Operators
Bit-wise operators do a bit-by-bit comparison between two operands. The
Operators included in Bit-wise operation are:
4. Logical Operators
Logical operators are bit-wise operators and are used only for single-bit
operands. They return a single bit value, 0 or 1. They can work on integers or
groups of bits, expressions and treat all non-zero values as 1.
o ! (logical NOT)
o && (logical AND)
o || (logical OR)
5. Reduction Operators
Reduction operators are the unary form of the bitwise operators and operate
on all the bits of an operand vector. These also return a single-bit value. The
operators included in Reduction operation are:
6. Shift Operators
Shift operators are shifting the first operand by the number of bits specified
by the second operand in the syntax.
Vacant positions are filled with zeros for both directions, left and right shifts
(There is no use sign extension). The Operators included in Shift operation
are:
7. Concatenation Operator
o { }(concatenation)
8. Replication Operator
9. Conditional Operator
o (Condition) ?
Operands
1. Literals
Wires, regs, and parameters are the data types used as operands in Verilog
expressions. Bit-Selection "x[2]" and Part-Selection "x[4:2]"
Bit-selects and part-selects are used to select one bit and multiple bits,
respectively, from a wire, regs or parameter vector using square brackets
"[ ]".
3. Function Calls
It just places the function call as one of the types of operands. It is useful to
know the bit width of the return value of the function call.
A typical design flow follows the below structure and can be broken down
into multiple steps. Some of these phases happen in parallel and some in
sequentially.
Requirements
The first step is to collect the requirements, estimate the end product's
market value, and evaluate the number of resources required to do the
project.
Specifications
Architecture
Now, the architect gives a system-level view of how the chip should operate.
They will decide what all other components are required, what clock
frequencies they should run, and how to target power and performance
requirements.
They also decide on how the data should flow inside the chip. An example
would be the data flow when a processor fetches imaging data from the
system ram and executes them. Meanwhile, the graphics engine will execute
post-processed data from the previous batch dumped into another part of
memory and so on.
Digital Design
It's not practical to design such a system from basic building blocks such as
flip-flops and CMOS transistors.
Verification
To save time and reach functional closure, both the design and verification
teams operate in parallel, where the designers release an RTL version. The
verification team develops a testbench environment and test cases to test
the functionality of that RTL version.
If any of these tests fail, it might indicate a problem with the design, and a
"bug" will be raised on that design element. This bug will have to be fixed in
the next version of the RTL release from the design team.
This process goes on until there is a good level of confidence in the design's
functional correctness.
Logic Synthesis
Now we will convert this design into hardware schematic with real elements
such as combinational gates and flip-flops. This step is called synthesis.
Logic synthesis tools ensure that the netlist meets timing, area, and power
specifications. Typically, they have access to different technology node
processes and digital elements libraries and can make intelligent calculations
to meet all these different criteria.
These libraries are obtained from semiconductor fabs that provide data
characteristics for different components such as rise or fall times for flip-
flops, input-output time for combinational gates, etc.
Logic Equivalence
The gate-level netlist is checked for logical equivalence with the RTL.
Sometimes, a gate-level verification is performed where verification of
certain elements is done once again, the difference being this time it is at
the gate level and a lower level of abstraction.
Then, the netlist is inputted to the physical design flow, where automatic
place and the route are done with EDA tools' help. The Cadence
Encounter and Synopsys IC Compiler are good examples of these kinds of
tools.
This will select and place standard cells into rows, define ball maps for input
and output, create different metal layers, and place buffers to meet timing.
Once this process is done, a layout is generated and usually sent for
fabrication. This stage is usually handled by the physical design team, who
are well familiar with the technology node and physical implementation
details.
Validation
A million clock cycles would have finished in a second, and tracing back to
the exact time of error will be time-consuming.
If there are any real issues or design bugs found at this stage, this will have
to be fixed in RTL, re-verified, and all the steps that follow this will have to be
performed.
Even though there are multiple steps in the design flow, a lot of the design
activity is usually concentrated on the optimization and verification of the
RTL description of the circuit.
It is important to note that although EDA tools are available to automate the
processes, improper usage will lead to inefficient designs. Hence, a designer
has to make conscious choices during the design process.
The top layer is the system-level architecture that defines the various sub-
blocks and groups them based on functionality.
For example, a processor cluster can have multiple cache blocks, cores, and
cache coherence logic. All of this will be represented as a single block with
input and output signals.
On the next level, each sub-block is written in a hardware description
language to describe each block's functionality accurately.
For example, a controller block will have multiple Verilog files, each
describing a smaller functionality component.
For example, the digital circuit for a D latch contains NAND gates arranged in
a certain manner such that all combinations of D and E inputs produce an
output Q given by the truth table.
A truth table essentially gives permutation of all input signal levels and the
resulting output level.
The hardware schematic can also be derived from the truth table using K-
maps and Boolean logic. However, it is not useful to follow this method for
more complex digital blocks like controllers and processors.
The final step is the layout of these transistors in silicon using EDA tools to
be fabricated. Some device and technology knowledge would be required at
this level because different layouts end up having different physical
properties like resistance and capacitance, among other implications.
Design Styles
There are primarily two styles followed in the design of digital blocks, one is
top-down, and another is bottom-up methodologies.
1. Top-Down
In this methodology, a top-level block is first defined along with identifying
sub-modules required to build the top block.
We can also use the combination of both flows. Architects define the system-
level view of the design, and designers implement each of the functional
blocks' logic and get synthesized into gates.
A top-down style is followed until this point. However, these gates have been
built by following a bottom-up flow, starting with the smallest block's
physical layout in the best possible area, power, and performance.
These standard cells also have a hardware schematic. And these can be used
to obtain various information such as rise and fall in power, times, and other
delays.
These cells are made available to the synthesis tool, which picks and
instantiates them where required.
Verilog introduces several new data types. These data types make RTL
descriptions easier to write and understand.
The data storage and transmission elements found in digital hardware are
represented using a set of Verilog Hardware Description Language (HDL)
data types.
In Verilog, data types are divided into NETS and Registers. These data
types differ in the way that they are assigned and hold values, and also they
represent different hardware structures.
Types Description
Unlike in C, Verilog specifies the number of bits for the fixed-width types.
Types Description
We preferred logic because it is better than reg. We can use logic where we
have used reg or wire.
Type Description
Arrays
In Verilog, we can define scalar and vector nets and variables. We can also
define memory arrays, which are one-dimensional arrays of a variable type.
Verilog takes this a stage further and refines the concept of arrays and
permits more operations on arrays.
In Verilog, arrays may have either packed or unpacked dimensions, or
both.
Packed dimensions
Unpacked dimensions
It can be arranged in memory in any way that the simulator chooses. We can
reliably copy an array on to another array of the same type.
For arrays with different types, we have to use a cast, and there are rules for
how an unpacked type is cast to a packed type.
For these, the arrays or slices involved must have the same type and shape,
i.e., the same number and lengths of unpacked dimensions.
The packed dimensions may differ, as long as the array or slice elements
have the same number of bits. The permitted operations are:
Verilog also includes dynamic arrays (the number of elements may change
during simulation) and associative arrays (which have a non-contiguous
range).
Nets
Nets are used to connect between hardware entities like logic gates and
hence do not store any value.
Some net data types are wire, tri, wor, trior, wand, triand, tri0, tri1,
supply0, supply1, and trireg. A net data type must be used when a signal
is:
1. Wire
A wire represents a physical wire in a circuit and is used to connect gates or
modules. The value of a wire can be read, but not assigned to, in a function
or block.
A wire does not store its value but must be driven by a continuous
assignment statement or by connecting it to the output of a gate or module.
2. Wand (wired-AND)
The value of a wand depends on logical AND of all the drivers connected to
it.
3. Wor (wired-OR)
The value of wor depends on the logical OR of all the drivers connected to it.
4. Tri (three-state)
All drivers connected to a tri must be z, except one that determines the tri's
value.
Registers
A register is a data object that stores its value from one procedural
assignment to the next. They are used only in functions and procedural
blocks.
Reg is a Verilog variable type and does not necessarily imply a physical
register. In multi-bit registers, data is stored as unsigned numbers, and no
sign extension is done for what the user might have thought were two's
complement numbers.
Some register data types are reg, integer, time, and real.reg is the most
frequently used type.
Note: A reg need not always represent a flip-flop because it can also represent
combinational logic.
o The reg variables are initialized to x at the start of the simulation. Any
wire variable not connected to anything has the x value.
o The size of a register or wire may be specified during the declaration.
o When the reg or wire size is more than one bit, then register and wire
are declared vectors.
Verilog String
Strings are stored in reg, and the width of the reg variable has to be large
enough to hold the string.
The initial statements are executed once, and the always statements are
executed repetitively.
Example
The initial statement is completed and not executed again during that
simulation run. This initial statement is containing a begin-end block of
statements. In this begin-end type block, a is initialized first, followed by b.
1.
module behave;
2. reg [1:0]a,b;
3.
4. initial
5. begin
6. a = 'b1;
7. b = 'b0;
8. end
9.
10. always
11. begin
12. #50 a = ~a;
13. end
14.
15. always
16. begin
17. #100 b = ~b;
18. end
19. End module
Procedural Assignments
The procedural assignments update the value of register variables under the
control of the procedural flow constructs that surround them.
Delay in Assignment
If another procedure changes a right-hand side signal during Δt, it does not
affect the output. Synthesis tools do not support delays.
Syntax
Blocking Assignments
The statement does not prevent the execution of statements that follow it in
a parallel block.
Syntax
Syntax
Step 1: The simulator evaluates the right-hand side and schedules the new
value assignment at a time specified by a procedural timing control.
Step 2: At the end of the time step, when the given delay has expired, or
the appropriate event has taken place, the simulator executes the
assignment by assigning the value to the left-hand side.
Conditions
Syntax
1. <statement>
2. ::= if ( <expression> ) <statement_or_null>
3. ||= if ( <expression> ) <statement_or_null>
4. else <statement_or_null>
5. <statement_or_null>
6.
7. ::= <statement>
8. ||= ;
The case statement is useful for describing, for example, the decoding of a
microprocessor instruction.
Syntax
1. <statement>
2. ::= case ( <expression> ) <case_item>+ endcase
3. ||= casez ( <expression> ) <case_item>+ endcase
4. ||= casex ( <expression> ) <case_item>+ endcase
5. <case_item>
6. ::= <expression> <,<expression>>* : <statement_or_null>
7. ||= default : <statement_or_null>
8. ||= default <statement_or_null>
o The case expressions are evaluated and compared in the exact order
in which they are given.
o During the linear search, if one of the case item expressions matches
the expression in parentheses, then the statement associated with that
case item is executed.
o If all comparisons fail, and the default item is given, then the default
item statement is executed.
o If the default statement is not given, and all of the comparisons fail,
none of the case item statements are executed.
The case statement differs from the multi-way if-else-if construct in two
essential ways, such as:
2. The case statement provides a definitive result when there are x and z
values in an expression.
Looping Statements
There are four types of looping statements. They are used to controlling the
execution of a statement zero, one, or more times.
Step 2: Evaluates an expression. Suppose the result is zero, then the for
loop exits. And if it is not zero, for loop executes its associated statements
and then performs step 3.
Syntax
The following are the syntax rules for the looping statements, such as:
1. <statement>
2. ::= forever <statement>
3. ||=forever
4. begin
5. <statement>+
6. end
7.
8.
9. <Statement>
10. ::= repeat ( <expression> ) <statement>
11. ||=repeat ( <expression> )
12. begin
13. <statement>+
14. end
15.
16.
17. <statement>
18. ::= while ( <expression> ) <statement>
19. ||=while ( <expression> )
20. begin
21. <statement>+
22. end
23.
24.
25. <statement>
26. ::= for ( <assignment> ; <expression> ; <assignment> )
27. <statement>
28. ||=for ( <assignment> ; <expression> ; <assignment> )
29. begin
30. <statement>+
31. end
Delay Controls
Verilog handles the delay controls in the following ways, such as:
1. Delay Control
1. <statement>
2. ::= <delay_control> <statement_or_null>
3. <delay_control>
4. ::= # <NUMBER>
5. ||= # <identifier>
6. ||= # ( <mintypmax_expression> )
2. Event Control
1. <statement>
2. ::= <event_control> <statement_or_null>
3.
4. <event_control>
5. ::= @ <identifier>
6. ||= @ ( <event_expression> )
7.
8. <event_expression>
9. ::= <expression>
10. ||= posedge <SCALAR_EVENT_EXPRESSION>
11. ||= negedge <SCALAR_EVENT_EXPRESSION>
12. ||= <event_expression> <or <event_expression>>
Value changes on nets and registers can be used as events to trigger the
execution of a statement. This is known as detecting an implicit event.
Verilog syntax also used to detect change based on the direction of the
change, which is toward the value 1 (posedge) or the value 0 (negedge).
The behavior of posedge and negedge for unknown expression values are:
Procedures
All procedures in Verilog are specified within one of the following four Blocks.
1. Initial blocks
2. Always blocks
3. Task
4. Function
Initial Blocks
The initial and always statements are enabled at the beginning of the
simulation. The initial blocks execute only once, and its activity dies when
the statement has finished.
Syntax
1. <initial_statement>
2. ::= initial <statement>
Example
The following example illustrates the use of the initial statement for the
initialization of variables it the starting of simulation.
1. Initial
2. Begin
3. Areg = 0; // initialize a register
4. For (index = 0; index < size; index = index + 1)
5. Memory [index] = 0; //initialize a memory
6. Word
7. End
1. Initial
2. Begin
3. Inputs = 'b000000;
4. // initialize at time zero
5. #10 inputs = 'b011001; // first pattern
6. #10 inputs = 'b011011; // second pattern
7. #10 inputs = 'b011000; // third pattern
8. #10 inputs = 'b001000; // last pattern
9. End
Always Blocks
The always blocks repeatedly executes. Its activity dies only when the
simulation is terminated. There is no limit to the number of initial and always
blocks defined in a module.
Syntax
1. <always_statement>
2. ::= always <statement>
The always statement is only useful when used in conjunction with some
form of timing control because of its looping nature.
Tasks and functions are procedures that are enabled by one or more places
in other procedures.
Verilog Module
Syntax
Note: The ports declared in the list of port declarations cannot be re-declared within the
module's body.
Purpose of a Module
Any combination of inputs can be given to the module, and it will provide a
corresponding output.
Hardware Schematic
The below GPU engine is divided into five different sub-blocks where each
performs a specific functionality.
The bus interface unit gets data from outside into the design, which gets
processed by another unit to instructions extraction. Other units down the
line process data provided by the previous unit.
Each sub-block can be represented as a module with a specific set of input
and output signals for communication with other modules, and each sub-
block can be further divided into more sub-sub-blocks as required.
Top-level Modules
A top-level module is one that contains all other modules. A top-level module
is not instantiated within any other module.
But, the testbench is not instantiated within any other module because it is
a block that encapsulates everything else.
The design code shown below has a top-level module called design. It
contains all other sub-modules required to make the design complete.
The sub-module can have a more nested sub-module, such as mod3 inside
mod1 and mod4 inside mod2.
1. // Design code
2.
3. module mod3 ( [port_list] );
4. reg c;
5.
6. // Design code
7. endmodule
8.
9. module mod4 ( [port_list] );
10. wire a;
11. // Design code
12. endmodule
13.
14. module mod1 ( [port_list] );
15. wire y;
16.
17.
18. mod3 mod_inst1 ( );
19.
20. mod3 mod_inst2 ( );
21.
22. endmodule
23.
24. module mod2 ( [port_list] );
25.
26. mod4 mod_inst1 ( );
27.
28. mod4 mod_inst2 ( );
29.
30. endmodule
31.
32. // Top-level module
33.
34. module design ( [port_list]);
35.
36. wire _net;
37. mod1 mod_inst1 ( );
38.
39. mod2 mod_inst2 ( );
40.
41. endmodule
Hence the design is instantiated and called d0 inside the testbench module.
The testbench is the top-level module from a simulator perspective.
1. //------------
2. // Testbench code
3. // this is the top-level module from simulation perspective
4. // because 'design' is instantiated within this module
5. //------------
6. module testbench;
7. design d0 ( [port_list_connections] );
8. //-----------
9.
10. endmodule
Hierarchical Names
Since each lower module instantiates within a given module, which should
have different identifier names, there will not be any ambiguity in accessing
signals.
A hierarchical name is constructed by a list of these identifiers separated by
dots (.) for each level of the hierarchy. Any signal can be accessed within any
module using the hierarchical path to that particular signal.
RTL Verilog
For example, a simple synchronous circuit is shown in the below image. The
inverter is connected from the output Q to the register's input D to create a
circuit. It changes its state on each rising edge of the CLK. In this circuit, the
combinational logic consists of the inverter.
While designing digital integrated circuits with a hardware description
language, the designs are usually arranged at a higher level of abstraction
than the transistor level or logic gate level.
This level is called the register-transfer level or RTL. The term RTL
focuses on describing the flow of signals between registers.
If there are logic paths from a register to another without a cycle, then it is
called a pipeline.
RTL is used in the logic design phase of the integrated circuit design cycle.
An RTL description is converted into a gate-level description of the circuit by
a logic synthesis tool.
The synthesis results are then used by placement and routing tools to create
a physical layout. Logic simulation tools may use a design's RTL description
to verify its correctness.
The most accurate power analysis tools are available for the circuit level, but
even with a switch rather than device-level modelling, tools at the circuit
level have disadvantages. They are either too slow or require too much
memory.
The majorities of these are simulators like SPICE and used by the designers
for many years as performance analysis tools
But it also has its trade-off as speedup is achieved on the cost of accuracy,
especially in the presence of correlated signals.
Over the years, it has been realized that the low power design cannot come
from the circuit- and gate-level optimizations. In contrast, system,
architecture, and algorithm optimizations tend to have the largest impact on
power consumption. Therefore, there has been a shift in the tool developers'
incline towards high-level analysis and optimization tools for power.
Disadvantages
Advantages
1. MSB: The most significant bit of constant expression, which is the left-
hand value of the range.
2. LSB: The least significant bit of constant expression, which is the right-
hand value of the range.
The LSB constant expression can be higher, equal, or less than the MSB
constant expression.
Both the MSB and the LSB expressions should be constant expressions.
Vectors can be declared for all types of net data types and reg data types.
Specifying vectors for integer, real, realtime, and time data types is
illegal. Vector nets and registers are treated as unsigned values.
Syntax
Examples
The range gives the ability to address individual bits in a vector. The most
significant bit of the vector should be specified as the left-hand value in the
range. While the least significant bit of the vector should be specified on the
right.
The MSB and LSB should be a constant expression and cannot be substituted
by a variable. But they can be any integer value such as positive, negative,
or zero.
The LSB value can be higher than, less than, or equal to the MSB value.
Bit Selects
If the bit select is out of bounds or the bit select is x or z, then the value
returned will be x.
Part Selects
The selection of the range of contiguous bits is called the part selected.
There are two types of part selects.
Syntax
Example
1. module block;
2. reg [31:0] data;
3. int i;
4. initial begin
5. data = 32'hFACE_CAFE;
6. for (i = 0; i < 4; i++) begin
7. $display ("data[8*%0d +: 8] = 0x%0h", i, data[8*i +: 8]);
8. end
9. $display ("data[7:0] = 0x%0h", data[7:0]);
10. $display ("data[15:8] = 0x%0h", data[15:8]);
11. $display ("data[23:16] = 0x%0h", data[23:16]);
12. $display ("data[31:24] = 0x%0h", data[31:24]);
13. end
14. endmodule
Verilog Arrays
Each array dimension is declared by having the min and max indices within
the square brackets. Array indices can be written in either direction:
1. array_name[least_significant_index:most_significant_index]
2. array_name[most_significant_index:least_significant_index]
Any square brackets before the array identifier are part of the data type
replicated in the array.
In Verilog-2001, all data types can be declared as arrays. The wire, reg, and
all other net types can also have a vector width declared. A dimension
declared before the object name is referred to as the vector
width dimension.
In Verilog, the term packed array refers to the dimensions declared before
the object name.
Packed arrays can be made of only the single-bit data types bit, logic, reg,
enumerated types, and other packed arrays and packed structures. This also
means we cannot have packed arrays of integer types with predefined
widths.
The maximum size of a packed array can be limited but shall be at least
65536 (216) bits.
Unpacked Arrays
Unpacked arrays can be made of any data type. Each fixed-size dimension is
represented by an address range, such as [0:1023].
Multi-dimensional Arrays
An array slice can only apply to one dimension; other dimensions must have
single index values in an expression.
Verilog arrays support many more operations than their traditional Verilog
counterparts.
o +: and -: Notation
When accessing a range of a Verilog array slice, we can specify a variable
slice by using the [start+: increment width] and [start-: decrement width]
notations.
They are simpler than needing to calculate the exact start and end indices
when selecting a variable slice. The increment or decrement width must be a
constant.
Verilog arrays support many more operations. The following operations can
be performed on both packed and unpacked arrays.
The list can contain values for individual array elements, or a default value
for the entire array.
1. logic [7:0] a, b, c;
2. logic [7:0] d_array[0:3];
3. logic [7:0] e_array[3:0]; // note index of unpacked dimension is reve
rsed
4.
5. logic [7:0] mult_array_a[3:0][3:0];
6. logic [7:0] mult_array_b[3:0][3:0];
7.
8. always_ff @(posedge clk, negedge rst_n)
9. if (!rst_n) begin
10. d_array <= '{default:0}; // assign 0 to all elem
ents of array
11. end
12.
13. else begin
14. d_array <= '{A00, c, b, a}; // d_array[0]=A00, d_arr
ay[1]=c,
15. d_array[2]=b, d_array[
3]=a
16.
17. e_array <= '{A00, c, b, a}; // e_array[3]=A00, e_arr
ay[2]=c,
18. e_array[1]=b, d_array[
0]=a
19.
20. mult_array_a <= '{'{A00, A01, A02, A03}, '{A04, A05, A06, A07},
21. '{A08, A09, A0a, A0b},
22. '{A0c, A0d, A0e, A0f}}; // assign to full
array
23. mult_array_b[3] <= '{A00, A01, A02, A03}; // assign to slice of a
rray
24. end
Verilog Ports
Port is an essential component of the Verilog module. Ports are used to communicate for a
module with the external world through input and output.
It communicates with the chip through its pins because of a module as a fabricated chip placed
on a PCB.
Every port in the port list must be declared as input, output or inout. All ports declared as
one of them is assumed to be wire by default to declare it, or else it is necessary to declare it
again.
Ports, also referred to as pins or terminals, are used when wiring the module to other modules.
o If the module does not exchange any signals with the environment,
there are no ports in the list.
o Consider a 4-bit full adder that is instantiated inside a top-level
module.
o The module fulladd4 takes input on ports a, b, and c_in and produces
an output on ports sum and c_out.
Port Declaration
Each port in the port list is defined as input, output, or inout based on the
port signal's direction.
If a port declaration includes the net or variable types, then that port is
considered completely declared. It is illegal to declare the same port in a net
or variable type declaration.
And if the port declaration does not include a net or variable type, then the
port can be declared again in a net or variable type declaration.
For example, consider the ports for top and full adder shown in the above
image.
Input and inout ports are generally declared as wires. However, if output
ports hold their value, they must be declared as reg as shown below:
NOTE: Ports of the type input and inout cannot be declared as reg.
Another main reason for connecting ports by name is that as long as the port
name is not changed, the order of ports in the port list of a module can be
rearranged without changing the port connections in module instantiations.
Ports Variations
o Verilog has undergone a few research, and the original IEEE version
in 1995 had the following way for port declaration.
Here the module declaration had to first list of the names of ports within the
brackets. And then the direction of those ports defined later within the body
of the module.
o ANSI-C style port naming was introduced in 2001. It allowed the type
to be specified inside the port list.
Assign statements are used to drive values on the net. And it is also used
in Data Flow Modeling.
This concept is realized by the assign statement where any wire or other
similar wire (data-types) can be driven continuously with a value. The value
can either be a constant or an expression comprising of a group of signals.
Syntax
The assignment syntax starts with the keyword assign, followed by the signal
name, which can be either a signal or a combination of different signal nets.
The drive strength and delay are optional and mostly used for dataflow
modeling than synthesizing into real hardware.
The signal on the right-hand side is evaluated and assigned to the net or
expression of nets on the left-hand side.
Delay values are useful for specifying delays for gates and are used to model
timing behavior in real hardware. The value dictates when the net should be
assigned with the evaluated value.
Rules
Reg signals can only be driven in procedural blocks such as always and
initial.
When an assign statement is used to assign the given net with some value, it
is called an explicit assignment
1. wire [1:0] a;
2. assign a = x & y; // Explicit assignment
3. wire [1:0] a = x & y; // Implicit assignment
Consider the following digital circuit made from combinational gates and the
corresponding Verilog code.
Verilog Operators
1. Arithmetic Operators
For the FPGA, division and multiplication are very expensive, and sometimes
we cannot synthesize division. If we use Z or X for values, the result is
unknown. The operations treat the values as unsigned.
+ Add b + c = 11
- Subtrac b - c = 9, -b=-10
/ Divide b/a=2
* Multiply a * b = 50
% Modulus b%a=0
2. Bitwise Operators
Each bit is operated, the result is the size of the largest operand, and the
smaller operand is left extended with zeroes to the bigger operand's size.
3. Reduction Operators
These operators reduce the vectors to only one bit. If there are the
characters z and x, the result can be a known value.
4. Relational Operators
== Equality a == b = 1'b0
!= Inequality a != b = 1'b1
5. Logical Operators
6. Shift Operators
These operators shift operands to the right or left, the size is kept constant,
shifted bits are lost, and the vector is filled with zeroes.
7. Assignment Operators
8. Other Operators
These are operators used for condition testing and to create vectors.
9. Operators Precedence
The order of the table tells what operation is made first. The first one has the
highest priority. The () can be used to override the default.
Operators precedence
+, -, !, ~ (Unary)
+,- (Binary)
<<, >>
<,>,<=,>=
==, !=
&
^, ^~ or ~^
&&
||
?:
An always block always executes, unlike initial blocks that execute only once
at the beginning of the simulation. The always block should have a sensitive
list or a delay associated with it
The sensitive list is the one that tells the always block when to execute the
block of code.
Syntax
1. always @ (event)
2. [statement]
3.
4. always @ (event) begin
5. [multiple statements]
6. end
Examples
The symbol @ after reserved word always, indicates that the block will be
triggered at the condition in parenthesis after symbol @.
1. always @ (x or y or sel)
2. begin
3. m = 0;
4. if (sel == 0) begin
5. m = x;
6. end else begin
7. m = y;
8. end
9. end
In the above example, we describe a 2:1 mux, with input x and y. The sel is
the select input, and m is the mux output.
NOTE: It can drive reg and integer data types but cannot drive wire data types.
There are two types of sensitive list in the Verilog, such as:
The code below is the same 2:1 mux, but the output m is now a flip-flop
output.
NOTE: The always block is executed at some particular event. A sensitivity list defines
the event.
Sensitivity List
In the code shown below, all statements inside the always block executed
whenever the value of signals x or y change.
If the sensitivity list is empty, there should be some other form of time delay.
Simulation time is advanced by a delay statement within the always
construct.
Now, the clock inversion is done after every 10-time units. That's why the
real Verilog design code always requires a sensitivity list.
Similarly, a combinational block becomes active when one of its input values
change. These hardware blocks are all working concurrently independently
of each other. The connection between each is what determines the flow of
data.
In the following example, all statements within the always block executed at
every positive edge of the signal clk
The below code defines a module called tff that accepts a data input, clock,
and active-low reset. Here, the always block is triggered either at the
positive edge of the clk or the negative edge of rstn.
The following events happen at the positive edge of the clock and are
repeated for all positive edge of the clock.
Step 1: First, if statement checks the value of active-low reset rstn.
o Check the value of d, and if it is found to be one, then invert the value
of q.
o If d is 0, then maintain value of q.
Step 1: First, if statement checks the value of active-low reset rstn. At the
negative edge of the signal, its value is 0.
For example, the digital circuit below represents three different logic gates
that provide a specific output at signal o.
The code shown below is a module with four input ports and a single output
port called o. The always block is triggered whenever any of the signals in
the sensitivity list changes in value.
The output signal is declared as type reg in the module port list because it is
used in a procedural block. All signals used in a procedural block should be
declared as type reg.
The always block indicates a free-running process, but the initial block
indicates a process executes exactly once. Both constructs begin execution
at simulator time 0, and both execute until the end of the block.
Initial and always block describe independent processes, which means the
statements in one process execute autonomously.
Syntax
1. initial
2. [single statement]
3.
4. initial begin
5. [multiple statements]
6.
7. end
The initial blocks do not have more purpose than to be used in simulations.
These blocks are primarily used to initialize variables and drive design ports
with specific values.
The image shown above has a module called behave, which has a and b
internal signals.
The initial block has only one statement, and hence it is not necessary to
place the statement within begin and end.
This statement assigns the value 2'b10 to a when the initial block is started
at time 0 units.
For example, If a is assigned first with the given value and then after 10-time
units, b is assigned to 0.
There are no limits to the number of initial blocks that can be defined inside
a module. The code shown below has three initial blocks, all of which are
started at the same time and run in parallel.
However, depending on the statements and the delays within each initial
block, the time taken to finish the block may vary.
NOTE: $finish is a Verilog system task that tells the simulator to terminate the current
simulation.
In the above image, the first block has a delay of 20 units, while the second
has a total delay of 50 units (10 + 40), and the last block has a delay of 60
units. Hence the simulation takes 60-time units to complete since there is
atleast one initial block still running until 60-time units.
If the last block had a delay of 30-time units, as shown below, the simulation
would have ended at 30-time units, thereby killing all the other initial blocks
that are active at that time.
1. initial begin
2. #30 $finish;
3. end
Verilog Block Statements
The block statements are the grouping of two or more statements together,
which act syntactically like a single statement. There are two types of blocks
in the Verilog:
o Sequential block
o Parallel block
These blocks can be used if more than one statement should be executed.
All statements in the sequential blocks will be executed sequentially in the
given order.
If a timing control statement appears within a block, then the next statement
will be executed after that delay. The sequential block shall be delimited by
the keywords begin and end.
All statements in the parallel blocks are executed at the same time or
concurrently. It means that the next statement's execution will not be
delayed even if the previous statement contains a timing control statement.
The parallel block shall be delimited by the keywords fork and join.
Sequential Block
Statements are wrapped using begin and end keywords and executed
sequentially in the given order. Delay values are treated relative to the time
of execution of the previous statement.
After all the statements within the block are executed, control may be
passed to some other place.
Syntax
1. begin: name
2. statement1;
3. …………..
4. end
Characteristics
The sequential block has the following characteristics, such as:
Example
1. module design0;
2. bit [31:0] data;
3.
4. // initial block starts at time 0
5.
6. initial begin
7.
8. // After 10 time units, data becomes 0xfe
9. #10 data = 8'hfe;
10. $display ("[Time=%0t] data=0x%0h", $time, data);
11.
12. // After 20 time units, data becomes 0x11
13. #20 data = 8'h11;
14. $display ("[Time=%0t] data=0x%0h", $time, data);
15. end
16. endmodule
In the above example, first statement in begin and end block will be
executed at 10 time units, and the second statement at 30 time units
because of the relative nature. It is 20 time units after execution of the
previous statement.
1. ncsim> run
2. [Time=10] data=0xfe
3. [Time=30] data=0x11
4. ncsim: *W,RNQUIE: Simulation is complete.
Parallel Block
A parallel block can execute statements concurrently, and delay control can
be used to provide the assignments' time-ordering. Statements are launched
in parallel by wrapping them within the fork and join keywords.
Syntax
1. fork: name
2. statement;
3. …………
4. join
Characteristics
Example
1. initial begin
2. #10 data = 8'hfe;
3. fork
4. #20 data = 8'h11;
5. #10 data = 8'h00;
6. join
7. end
In the above example, fork and join block will be launched after executing
the statement at 10-time units.
Statements within this block will be executed in parallel, and the first
statement will be the one where data is assigned a value of 8'h00 since the
delay for that is 10-time units after the fork-join launch.
After 10 more time units, the first statement will be launched and data will
get the value 8'h11.
1. initial begin
2. #10 data = 8'hfe;
3. fork
4. #10 data = 8'h11;
5. begin
6. #20 data = 8'h00;
7. #30 data = 8'haa;
8. end
9. join
10. end
There is a begin-end block in the above example, and all statements within
the begin-end block will be executed sequentially. Still, the block itself will be
launched in parallel, along with the other statements. Data will get 8'h11 at
20-time units, 8'h00 at 30-time units, and 8'haa at 60-time units.
Naming of blocks
Both sequential and parallel blocks can be named by
adding name_of_block after the begin and fork keywords. By doing this,
the block can be referenced in a disable statement.
1. begin: name_seq
2. [statements]
3. end
4. fork: name_fork
5. [statements]
6. join
Verilog Assignments
Placing values onto variables and nets are called assignments. There are
three necessary forms:
1. Procedural
2. Continuous
3. Procedural continuous
Procedural
o Variables (vector or scalar)
o Bit-select or part-select of an integer, vector reg, or
time variable.
o Memory word.
o Concatenation of any of the above.
Continuous
o Net (vector or scalar)
o Bit-select or part-select of a vector net.
o Concatenation of bit-selects and part-selects.
Procedural
o Variable or net (scalar/vector)
Continuous
o Part-select or bit-select of a vector net.
The RHS can contain any expression that evaluates to a final value while the
LHS indicates a variable or net to which RHS's value is being assigned.
Procedural Assignment
Procedural assignments occur within procedures such as initial, always,
task, and functions are used to place values onto variables. The variable
will hold the value until the next assignment to the same variable.
The value will be placed onto the variable when the simulation executes this
statement during simulation time. This can be modified and controlled the
way we want by using control flow statements such as if-else-if, looping,
and case statement mechanisms.
An initial value can be placed onto a variable at the time of its declaration.
The assignment does not have the duration and holds the value until the
next assignment to the same variable happens.
NOTE: The variable declaration assignments to an array are not allowed.
1. module my_book;
2. reg [31:0] data = 32'hdead_cafe;
3. initial begin
4. #20 data = 32'h1234_5678; // data will have dead_cafe from time 0 to 20
5. end
6. endmodule
7. reg [3:0] a = 4'b4;
8. reg [3:0] a;
9. initial a = 4'b4;
1. module my_book;
2. reg [7:0] addr = 8'h05;
3. initial
4. addr = 8'hee;
5. endmodule
6. reg [3:0] array [3:0] = 0; // illegal
7. integer i = 0, j; // declares two integers i,j and 0 is assigned
to i
8. real r2 = 4.5, r3 = 8; // declares two real numbers r2,r3 and are assigne
d 4.5, 8 resp.
9. time startTime = 40; // declares time variable with initial value 40
Continuous Assignment
This is used to assign values onto scalar and vector nets. And it happens
whenever there is a change in the RHS.
NOTE: Only one declaration assignment is possible because a net can be declared only
once.
1. wire penable = 1;
The value of the variable will remain the same until the variable gets a new
value through a procedural or procedural continuous assignment.
1. reg q;
2. initial begin
3. assign q = 0;
4. #10 deassign q;
5. end
The force statement will override all other assignments made to the variable
until it is released using the release keyword.
1. reg o, a, b;
2. initial begin
3. force o = a & b;
4. ……….
5. release o;
6. end
The blocking assignment is similar to software assignment statements found in most popular
programming languages. The non-blocking assignment is the more natural assignment statement
to describe many hardware systems, especially for synthesis.
The blocking assignments can only be used in a few situations, such as modeling combinational
logic, defining functions, or implementing testbench algorithms. All IEEE P1364.1 compliant
synthesis tools are required to support both blocking and non-blocking assignments in explicit-
style code, with the restriction that each variable and each block may use only one or the other
kind of assignment.
Blocking Assignment
Blocking assignment statements are assigned using (=) operator and are
executed one after the other in a procedural block. But, it will not prevent
the execution of statements that run in a parallel block.
1. module Block;
2. reg [7:0] a, b, c, d, e;
3.
4. initial begin
5. a = 8'hDA;
6. $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
7. b = 8'hF1;
8. $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
9. c = 8'h30;
10. $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
11. end
12.
13. initial begin
14. d = 8'hAA;
15. $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
16. e = 8'h55;
17. $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
18. end
19. endmodule
There are two initial blocks which are executed in parallel. Statements are executed
sequentially in each block and both blocks finish at time 0ns.
To be more specific, variable is assigned first, that followed by the display statement which is
then followed by all other statements.
This is visible in the output where variable b and c are 8'hxx in the first display statement. This is
because variable b and c assignments have not been executed yet when the first $display is
called.
1. ncsim> run
2. [0] a=0xda b=0xx c=0xx
3. [0] a=0xda b=0xf1 c=0xx
4. [0] a=0xda b=0xf1 c=0x30
5. [0] d=0xaa e=0xx
6. [0] d=0xaa e=0x55
7. ncsim: *W,RNQUIE: Simulation is complete.
In the below example, we'll add a few delays into the same set of statements to see how it reacts
and behaves.
1. module Block;
2. reg [7:0] a, b, c, d, e;
3. initial begin
4. a = 8'hDA;
5. $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
6. #10 b = 8'hF1;
7. $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
8. c = 8'h30;
9. $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
10. end
11. initial begin
12. #5 d = 8'hAA;
13. $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
14. #5 e = 8'h55;
15. $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
16. end
17. endmodule
1. ncsim> run
2. [0] a=0xda b=0xx c=0xx
3. [5] d=0xaa e=0xx
4. [10] a=0xda b=0xf1 c=0xx
5. [10] a=0xda b=0xf1 c=0x30
6. [10] d=0xaa e=0x55
7. ncsim: *W,RNQUIE: Simulation is complete.
Non-blocking Assignment
Non-blocking assignment statements are allowed to be scheduled without blocking the execution
of the following statements and is specified by a (<=) symbol.
The same symbol is used as a relational operator in expressions, and as an assignment operator in
the context of a non-blocking assignment.
Take the same example as above, replace all (=) symbols with a non-blocking assignment
operator (<=), we'll get the difference in the output.
1. module Block;
2. reg [7:0] a, b, c, d, e;
3. initial begin
4. a <= 8'hDA;
5. $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
6. b <= 8'hF1;
7. $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
8. c <= 8'h30;
9. $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
10. end
11. initial begin
12. d <= 8'hAA;
13. $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
14. e <= 8'h55;
15. $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
16. end
17. endmodule
Now, all the $display statements printed 'h'x. The reason for this behavior
is the execution of the non-blocking assignment statements.
The captured RHS value is assigned to the LHS variable only at the end of
the time-step.
1. ncsim> run
2. [0] a=0xx b=0xx c=0xx
3. [0] a=0xx b=0xx c=0xx
4. [0] a=0xx b=0xx c=0xx
5. [0] d=0xx e=0xx
6. [0] d=0xx e=0xx
7. ncsim: *W,RNQUIE: Simulation is complete.
if-else-if
This conditional statement is used to decide whether certain statements will
be executed or not. It is very similar to the if-else-if statements in C
language. If the expression evaluates to true, then the first statement will be
executed.
If the expression evaluates to false and if an else part exists, the else part
will be executed.
Syntax
The else part of an if-else is optional, and it can create confusion. To avoid
this confusion, it's easier to always associate the else to the previous if that
lacks an else. Another way is to enclose statements within a begin-end block.
The last else part handles the none-of-the-above or default case where none
of the other conditions were satisfied.
1. Forever loop
This loop will continuously execute the statements within the block.
Syntax
1. Forever
2. [statement]
3. forever begin
4. [multiple statements]
5. end
Example
1. module my_block;
2. initial begin
3. forever begin
4. $display ("This will be printed forever, simulation can hang ...");
5. end
6. end
7. endmodule
After the execution of the above example, it produces the following data.
1. ncsim> run
2. This will be printed forever, simulation can hang
3. This will be printed forever, simulation can hang
4. ...
5. ...
6. This will be printed forever, simulation can hang
7. This will be printed forever, simulation can hang
8. This will be printed forever, simulation can hang
9. This will be printed forever, simulation can hang
10. …
11. …
12. Result reached a maximum of 5000 lines.
13. Killing process.
2. Repeat loop
This will execute statements a fixed number of times. If the expression
evaluates to an X or Z, it will be treated as zero and not executed.
Syntax
Example
1. module my_block;
2. initial begin
3. repeat(5) begin
4. $display("This is a new iteration ...");
5. end
6. end
7. endmodule
1. ncsim> run
2. This is a new iteration
3. This is a new iteration
4. This is a new iteration
5. This is a new iteration
6. This is a new iteration
7.
8. ncsim: *W,RNQUIE: Simulation is complete.
3. ile loop
This will execute statements as long as an expression is true and will exit
once the condition becomes false. If the condition is false from the start,
statements will not be executed at all.
Syntax
Example
1. module my_block;
2. integer i = 5;
3. initial begin
4. while (i > 0) begin
5. $display ("Iteration#%0d", i);
6. i = i - 1;
7. end
8. end
9. endmodule
Run the above code, and we will get the following output.
1. ncsim> run
2. Iteration#5
3. Iteration#4
4. Iteration#3
5. Iteration#2
6. Iteration#1
7. ncsim: *W,RNQUIE: Simulation is complete.
4. For loop
For loop controls the statements using a three-step process:
Syntax
Example
1. module my_block;
2. integer i = 5;
3. initial begin
4. for (i = 0; i < 5; i = i + 1) begin
5. $display ("Loop #%0d", i);
6. end
7. end
8. endmodule
After execution the for loop code, the output looks like
1. ncsim> run
2. Loop #0
3. Loop #1
4. Loop #2
5. Loop #3
6. Loop #4
7. ncsim: *W,RNQUIE: Simulation is complete.
Verilog Functions
The purpose of a function is to return a value that is to be used in an
expression. A function definition always starts with the function keyword
followed by the return type, name, and a port list enclosed in parentheses.
And it ends with the endfunction keyword.
Functions should have at least one input declaration and a statement that
assigns a value to the register with the same name as the function. And the
return type can be void if the function does not return anything.
In this situation, we can declare a function and place the repetitive code
inside the function and allow it to return the result.
It will reduce the number of lines in the RTL. Now we need to do a function
call and pass data on which the computation needs to be performed.
Syntax
The keyword automatic will make the reentrant function and items declared
within the task that are dynamically allocated rather than shared between
different invocations of the task. This will be useful for recursive functions
and when the same function is executed concurrently by N processes.
Function declarations
A function declaration specifies the function's name, the function input
arguments, the variables (reg) used within the function, the width of the
function return value, and the function local parameters and integers.
Syntax
Example
Hence it is illegal to declare another variable of the same name inside the
scope of the function. The return value is initialized by assigning the function
result to the internal variable.
1. sum = a + b;
Calling a Function
A function call is an operand with an expression. A function call must specify
in its terminal list all the input parameters.
Example
Rules
The following are some of the general rules for the Verilog functions:
Verilog Task
A function is meant to do some processing on the input and return a single
value. In contrast, a task is more general and can calculate multiple result
values and return them using output and inout type arguments.
Data is passed to the task, the processing is done, and the result returned.
They have to be specifically called, with the data ins and outs, rather than
just wired into the general netlist.
Included in the main body of code, they can be called many times, reducing
code repetition.
o Tasks are defined in the module in which they are used. It is possible
to define a task in a separate file and use the compiled directive,
including the task in the file, which instantiates the task.
o Tasks can include timing delays, such as posedge, negedge, #
delay, and wait.
o Tasks can have any number of inputs and outputs.
o The variables declared within the task are local to that task. The order
of declaration within the task defines how the variables passed to the
task by the caller are used.
o Tasks can take, drive, and source global variables when no local
variables are used. When local variables are used, the output is
assigned only at the end of task execution.
o Tasks can call another task or function.
o Tasks can be used for modeling both combinational and sequential
logic.
Syntax
A task begins with keyword task and ends with keyword endtask. Inputs and
outputs are declared after the keyword task.
1. // Style 1
2.
3. task [name];
4. input [port_list];
5. inout [port_list];
6. output [port_list];
7. begin
8. [statements]
9. end
10. endtask
11.
12.
13. // Style 2
14.
15. task [name] (input [port_list], inout [port_list], output [port_list]);
16. begin
17. [statements]
18. end
19. endtask
The keyword automatic will make the reentrant task. Otherwise, it will be
static by default. If a task is static, all its member variables will be shared
across different invocations of the same task that has been launched
concurrently.
1. //Style 1
2. task sum (input [7:0] a, b, output [7:0] c);
3. begin
4. c = a + b;
5. end
6. endtask
7.
8. // Style 2
9. task sum;
10. input [7:0] a, b;
11. output [7:0] c;
12. begin
13. c = a + b;
14. end
15. endtask
16. initial begin
17. reg [7:0] x, y , z;
18. sum (x, y, z);
19. end
Global tasks
Tasks that are declared outside all modules are called global tasks as they
have a global scope and can be called within any module.
1. xcelium> run
2. Hello
3. xmsim: *W,RNQUIE: Simulation is complete.
Function Task
The function should have atleast one Tasks can have zero or more
input argument and cannot have output arguments of any type.
or inout arguments.
A function can return only a single value. It cannot return a value but can
achieve the same effect using output
arguments.
In Verilog, a case statement includes all of the code between the Verilog
keywords, case ("casez", "casex"), and endcase. A case statement can be a
select-one-of-many construct that is roughly like Associate in nursing if-else-
if statement.
Syntax
A Verilog case statement starts with the case keyword and ends with the
endcase keyword.
And the statements that the selection matches the given expression unit of
measurement dead. A block of multiple statements ought to be sorted and
be within begin and end.
1. case ()
2. case_item1 :
3. case_item2,
4. case_item3 :
5. case_item4 :
6. begin
7.
8. end
9. default:
10.
11. endcase
If none of the case things match the given expression, statements within the
default item unit of measurement dead. The default statement is non
mandatory, and there's only one default statement throughout a case
statement. Case statements are nested.
Execution will exit the case block whereas not doing one thing if none of the
items match the expression, and a default statement is not given.
Example
The following vogue module includes a 2-bit opt for signal to route one
among the three different 3-bit inputs to the sign stated as out.
A case statement is used to assign the correct input to output supported the
value of sel. Since sel can be a 2-bit signal, it'll have twenty 2 combos, zero
through 3. The default statement helps to line output to zero if sel is 3.
Case item
The case item is that the bit, vector, or Verilog expression accustomed
compare against the case expression.
Unlike different high-level programming languages like 'C', the Verilog case
statement includes implicit break statements.
The first case item that matches this case expression causes the
corresponding case item statement to be dead, thus all of the rest of the
case things unit of measurement skipped for this undergo the case
statement.
To alter the parsing of Verilog code document, Verilog case item statements
ought to be enclosed between the keywords "begin" and "end" if over one
statement is to be dead for a specific case item.
Casez
In Verilog, there is a casez statement, a variation of the case statement that
enables "z" and "?" values to be treated throughout case-comparison as
"don't care" values.
"Z" and "?" unit of measurement treated as a don't care if they are inside the
case expression or if they are inside the case item.
When secret writing a case statement with "don't care," use a casez
statement and use "?" characters instead of "z" characters inside the case
things to purpose "don't care" bits.
Casex
In Verilog, there is a casex statement, a variation of the case statement that
enables "z", "?", and "x" values to be treated throughout comparison as
"don't care" values.
"x", "z" and "?" unit of measurement treated as a don't care if they are inside
the case expression or if they are inside the case item.
If a case statement does not embrace a case default, and it's getable to go
looking out a binary case expression that does not match any of the printed
case things, the case statement is not full.
A full case statement can be a case statement inside that every getable
binary, non-binary, and mixture of binary and non-binary patterns is boxed in
as a case item inside the case statement.
Verilog does not would like case statements to be either synthesis or high-
density lipoprotein simulation full, but Verilog case statements is made full
by adding a case default. VHDL desires case statements to be high-density
lipoprotein simulation full, that usually desires Associate in Nursing "others"
clause.
Hardware Schematic
The RTL code is elaborated to get a hardware schematic that represents a 4
to 1 multiplexer.
After executes the above design, the output is zero when sel is 3 and
corresponds to the assigned inputs for other values.
1. ncsim> run
2. [0] a=0x4 b=0x1 c=0x1 sel=0b11 out=0x0
3. [10] a=0x5 b=0x5 c=0x5 sel=0b10 out=0x5
4. [20] a=0x1 b=0x5 c=0x6 sel=0b01 out=0x5
5. [30] a=0x5 b=0x4 c=0x1 sel=0b10 out=0x1
6. [40] a=0x5 b=0x2 c=0x5 sel=0b11 out=0x0
7. ncsim: *W,RNQUIE: Simulation is complete.
In a case statement, the comparison only succeeds when each bit of the
expression matches one of the alternatives including 0, 1, x and z. In the
above example, if any of the bits in sel is either x or z,
the default statement will be executed because none of the other
alternatives matched. In such a case, output will be all zeros.
1. ncsim> run
2. [0] a=0x4 b=0x1 c=0x1 sel=0bxx out=0x0
3. [10] a=0x3 b=0x5 c=0x5 sel=0bzx out=0x0
4. [20] a=0x5 b=0x2 c=0x1 sel=0bxx out=0x0
5. [30] a=0x5 b=0x6 c=0x5 sel=0bzx out=0x0
6. [40] a=0x5 b=0x4 c=0x1 sel=0bxz out=0x0
7. [50] a=0x6 b=0x5 c=0x2 sel=0bxz out=0x0
8. [60] a=0x5 b=0x7 c=0x2 sel=0bzx out=0x0
9. [70] a=0x7 b=0x2 c=0x6 sel=0bzz out=0x0
10. [80] a=0x0 b=0x5 c=0x4 sel=0bxx out=0x0
11. [90] a=0x5 b=0x5 c=0x5 sel=0bxz out=0x0
12. ncsim: *W,RNQUIE: Simulation is complete.
If the case statement in design has x and z in the case item alternatives, the
results will differ.
1. module mux (input [2:0] a, b, c, output reg [2:0] out);
2.
3. // Case items have x and z, and sel has to match the exact value for
4. // output to be assigned with the corresponding input
5.
6. always @ (a, b, c, sel) begin
7. case(sel)
8. 2'bxz: out = a;
9. 2'bzx: out = b;
10. 2'bxx: out = c;
11. default: out = 0;
12. endcase
13. end
14.
15. endmodule
Verilog Parameters
In Verilog, parameters are constants and do not belong to any other data
type such as register or net data types.
There are two types of parameters, module and specify, and both accept a
range specification. But, they are made as wide as the value to be stored
them, and hence a range specification is not necessary.
Module parameters
It can be used to override parameter definitions within a module and makes
the module have a different set of parameters at compile time. A parameter
can be modified with the defparam statement. It is common to use
uppercase letters in names for the parameter to notice them instantly.
The below module uses parameters to specify the bus width, data width and
the depth of FIFO within the design, and can be overridden with new values
when the module is instantiated or by using defparam statements.
In the new ANSI style of Verilog port declaration, we may declare parameters
such as:
1. module design_ip
2. #(parameter BUS_WIDTH=32,
3. parameter DATA_WIDTH=64)
4. (input [BUS_WIDTH-1:0] addr,
5. // other port declarations
6. );
Overriding parameters
Parameters can be overridden with new values during module instantiation.
The first part is the module called design_ip by the name d0 where new
parameters are passed within # ( ).
The second part is use a Verilog construct called defparam to set the new
parameter values. The first method is commonly used to pass new
parameters in RTL designs. And the second method is used in testbench
simulations to quickly update the design parameters without having to
reinstantiate the module.
1. module tb;
2. // Module instantiation override
3. design_ip #(BUS_WIDTH = 64, DATA_WIDTH = 128) d0 ( [port list]);
4. // Use of defparam to override
5. defparam d0.FIFO_DEPTH = 128;
6. endmodule
The module counter has two parameters N and DOWN, which is declared to
have a default value of 2 and 0.
N controls the number of bits in the output, effectively controlling the width
of the counter. It is a 2-bit counter by default.
2-bit up Counter
1. module counter
2. # ( parameter N = 2, parameter DOWN = 0)
3.
4. (input clk, input rstn, input en, output reg [N-1:0] out);
5.
6. always @ (posedge clk) begin
7. if (!rstn) begin
8. out <= 0;
9. end else begin
10. if (en)
11. if (DOWN)
12. out <= out - 1;
13. else
14. out <= out + 1;
15. else
16. out <= out;
17. end
18. end
19. endmodule
DOWN is not passed during module instantiation. And it takes the default
value of 0 making it an up-counter.
1. module design_top (input clk, input rstn, input en, output [1:0] out);
2. counter #(.N(2)) u0 (.clk(clk), .rstn(rstn), .en(en));
3. endmodule
The default parameters are used to implement the counter where N equals
two, making it a 2-bit counter, and DOWN equals zero, making it an up-
counter. The output from the counter is left unconnected at the top level.
1. module design_top (input clk, input rstn, input en, output [3:0] out);
2. counter #(.N(4), .DOWN(1))
3. u1 (.clk(clk), .rstn(rstn), .en(en));
4. endmodule
Specify Parameters
These parameters are used to provide time and delay values and declared
using the specparam keyword. It is allowed to use both within the specified
block and the main module body.
It can be declared inside a specific It can only be declared within the main
block or within the main module. module.
Notes
Here are some important notes for the Verilog parameters, such as:
o If we are using the defparam statement, we must specify a
hierarchical path to the parameter.
o We cannot skip over a parameter in a module instance parameter
value assignment. If we need to do this, use the initial value for a not
overwritten parameter.
o When one parameter depends on the other, then the second will
automatically be updated if we change the first one.
o Delay control
o Edge sensitive event control
o Level sensitive event control
o Named events
The delay control is a way of adding a delay between when the simulator
encounters the statement and when it executes.
Simulation time can be advanced by one of the following methods. Nets and
gates have been modeled to have internal delays, also advance simulation
time.
Delay Control
Delay control is achieved by specifying the waiting time to execution when
the statement is encountered. The symbol # is used to specify the delay.
Event Control
An event controls the execution of a statement or a block of a statement.
Value changes on variables and nets can be used as a synchronization event
to trigger the execution of other procedural statements and is
an implicit event.
The event is based on the direction of change like towards 0, which makes it
a negedge and change towards 1 make it a posedge.
A transition from the same state to the same state does not suppose to be
an edge. The edge event can be detected only on the LSB of a vector signal
or variable.
An event cannot hold any data, no time duration, and can be made to occur
at any particular time.
1. module tb;
2. event a_event;
3. event b_event[5];
4.
5. initial begin
6. #20 -> a_event;
7.
8. #30;
9. ->a_event;
10.
11. #50 ->a_event;
12. #10 ->b_event[3];
13. end
14.
15. always @ (a_event) $display ("T=%0t [always] a_event is triggered",
$time);
16.
17. initial begin
18. #25;
19. @(a_event) $display ("T=%0t [initial] a_event is triggered", $time);
20.
21. #10 @(b_event[3]) $display ("T=%0t [initial] b_event is triggered", $
time);
22. end
23.
24. endmodule
For example, the always block and the second initial block are
synchronized by a_event. Events can be declared as arrays such as in the
case of b_event, which is an array of size 5, and index 3 is used for trigger
and wait for purpose.
The or operator can wait until any one of the listed events is triggered in an
expression. The comma (,) can be used instead of the or operator.
The edge events are queued and then serviced by @(...) guards, rather than
@(?) being a guard that waits on edge events, blocking until an edge event
happens.
The only edge events that matter to an @(...) guard are those that happen
while it is waiting. Edge events that happen before reaching the guard are
irrelevant to the guard.
1. module tb;
2. reg [3:0] ctr;
3. reg clk;
4.
5. initial begin
6. {ctr, clk} <= 0;
7. wait (ctr);
8. $display ("T=%0t Counter reached non-zero value 0x%0h", $time, ctr);
9. wait (ctr == 4) $display ("T=%0t Counter reached 0x%0h", $time, ctr);
10. $finish;
11. end
12. always #10 clk = ~clk;
13. always @ (posedge clk)
14. ctr <= ctr + 1;
15. endmodule
1. ncsim> run
2. T=10 Counter reached non-zero value 0x1
3. T=70 Counter reached 0x4
4. T=90 Counter reached 0x5
5. T=170 Counter reached 0x9
6. Simulation complete via $finish(1) at time 170 NS + 1
1. module tb;
2. reg a, b, c, d;
3. reg x, y;
4.
5. // signals inside () after @ operator
6. // it is a, b, c or d
7.
8. always @ (a, b, c, d) begin
9. x = a | b;
10. y = c ^ d;
11. end
12.
13. initial begin
14. $monitor ("T=%0t a=%0b b=%0b c=%0b d=%0b x=%0b y=
%0b", $time, a, b, c, d, x, y);
15. {a, b, c, d} = 0;
16.
17. #10 {a, b, c, d} = $random;
18. #10 {a, b, c, d} = $random;
19. #10 {a, b, c, d} = $random;
20. end
21.
22. endmodule
Now execute the above code and we will get the following output:
ncsim> run
T=0 a=0 b=0 c=0 d=0 x=0 y=0
T=10 a=0 b=1 c=0 d=0 x=1 y=0
T=20 a=0 b=0 c=0 d=1 x=0 y=1
T=30 a=1 b=0 c=0 d=1 x=1 y=1
ncsim: *W,RNQUIE: Simulation is complete.
If the user decides to add new signal e and capture the inverse into z, then
special care must be taken to add e also into the sensitivity list.
1. module tb;
2. reg a, b, c, d, e;
3. reg x, y, z;
4.
5. // Add "e" also into sensitivity list
6. always @ (a, b, c, d, e) begin
7. x = a | b;
8. y = c ^ d;
9. z = ~e;
10. end
11.
12. initial begin
13. $monitor ("T=%0t a=%0b b=%0b c=%0b d=%0b e=%0b x=%0b y
=%0b z=%0b",
14. $time, a, b, c, d, e, x, y, z);
15. {a, b, c, d, e} = 0;
16.
17. #10 {a, b, c, d, e} <= $random;
18. #10 {a, b, c, d, e} <= $random;
19. #10 {a, b, c, d, e} <= $random;
20. end
21.
22. endmodule
ncsim> run
T=0 a=0 b=0 c=0 d=0 e=0 x=0 y=0 z=1
T=10 a=0 b=0 c=1 d=0 e=0 x=0 y=1 z=1
T=20 a=0 b=0 c=0 d=0 e=1 x=0 y=0 z=0
T=30 a=0 b=1 c=0 d=0 e=1 x=1 y=0 z=0
ncsim: *W,RNQUIE: Simulation is complete.
ncsim> run
T=0 a=0 b=0 c=0 d=0 e=0 x=0 y=0 z=1
T=10 a=0 b=0 c=1 d=0 e=0 x=0 y=1 z=1
T=20 a=0 b=0 c=0 d=0 e=1 x=0 y=0 z=0
T=30 a=0 b=1 c=0 d=0 e=1 x=1 y=0 z=0
ncsim: *W,RNQUIE: Simulation is complete.
Inter assignment are those delay statements where the execution of the
entire statement or assignment got delayed.
Example
1. module vd;
2. reg a, b, c, q;
3.
4. initial begin
5. $monitor("[%0t] a=%0b b=%0b c=%0b q=%0b", $time, a, b, c, q);
6.
7. // Now initialize all signals to 0 at time 0
8. a <= 0;
9. b <= 0;
10. c <= 0;
11. q <= 0;
12.
13. // Inter-assignment delay. Wait for #5 time units
14. // and then assign a and c to 1. Note that 'a' and 'c'
15. // gets updated at the end of current timestep
16. #5 a <= 1;
17. c <= 1;
18.
19. // Inter-assignment delay. Wait for #5 time units
20. // and then assign 'q' with whatever value RHS gets
21. // evaluated to
22. #5 q <= a & b | c;
23. #20;
24. end
25. endmodule
xcelium> run
[0] a=0 b=0 c=0 q=0
[5] a=1 b=0 c=1 q=0
[10] a=1 b=0 c=1 q=1
xmsim: *W,RNQUIE: Simulation is complete.
Example
1. module vd;
2. reg a, b, c, q;
3. initial begin
4. $monitor("[%0t] a=%0b b=%0b c=%0b q=%0b", $time, a, b, c, q);
5. // Initialize all signals to 0 at time 0
6. a <= 0;
7. b <= 0;
8. c <= 0;
9. q <= 0;
10. // Inter-assignment delay: Wait for #5 time units
11. // and then assign a and c to 1. Note that 'a' and 'c'
12. // gets updated at the end of current timestep
13. #5 a <= 1;
14. c <= 1;
15. // Intra-assignment delay: First execute the statement
16. // then wait for 5 time units and then assign the evaluated
17. // value to q
18. q <= #5 a & b | c;
19. #20;
20. end
21. endmodule
xcelium> run
[0] a=0 b=0 c=0 q=0
[5] a=1 b=0 c=1 q=0
xmsim: *W,RNQUIE: Simulation is complete.
At 5 time units, a and c are assigned using non-blocking statements. And the
behavior of non-blocking statements is evaluated but gets assigned to the
variable only at the end of the time step.
So the value of a and c is evaluated to 1 but not assigned when the next
non-blocking statement q is executed. So when RHS of q is evaluated, a and
c still has an old value of 0, and hence $monitor does not detect a change to
display the statement.
xcelium> run
[0] a=0 b=0 c=0 q=0
[5] a=1 b=0 c=1 q=0
[10] a=1 b=0 c=1 q=1
xmsim: *W,RNQUIE: Simulation is complete.
The gate delay declaration can be used in gate instantiations. The delays can
also be used for delay control in procedural statements.
Digital elements are binary entities and only hold either of the two values, 0
and 1. The transition from 0 to 1 and 1 to 0 has a transitional delay, and
therefor each gate element propagates the value from input to its output.
For example, a two-input AND gate has to switch the output to 1 if both
inputs become 1 and back to 0 when inputs become 0.
The net delay declaration specifies a time needed to propagate values from
drivers through the net. It can be used in continuous assignments and net
declarations.
This gate and pin to pin delays can be specified in Verilog when instantiating
logic primitives.
o The time taken for the output of a gate to change from some value to 1
is called a rise delay.
o The time taken for the output of a gate to change form some value to 0
is called a fall delay.
o The time taken for the output of a gate to change from some value to
high impedance is called turn-off delay.
If only one delay value is specified, then it is used for all signal changes. The
default delay is zero.
If two delays are specified, then the first delay specifies the rise delay, and
the second delay specifies the fall delay.
These delays apply to any signal as they all can rise or fall anytime in real
circuits and are not restricted to only outputs of gates. There are three ways
to represent gate delays.
The two delay format can be applied to most primitives whose outputs do
not transition to high impedance.
A three delay format cannot be applied to an AND gate because the output
will not go to Z for any input combination.
1. // Single delay specified - used for all three types of transition delays
2. or #(<delay>) o1 (out, a, b);
3.
4. // Two delays specified - used for Rise and Fall transitions
5. or #(<rise>, <fall>) o1 (out, a, b);
6. // Three delays specified - used for Rise, Fall and Turn-off transitions
7. or #(<rise>, <fall>, <turn_off>) o1 (out, a, b);
If only a single delay is specified, all three types of delays will use the same
given value.
If there are two delays specified, the first one represents the rise, and the
second one represents the fall delay.
If there are three delays specified, they represent rise, fall, and turn-
off delays, respectively.
Now, See that the output of AND gates change 2 time units after one of its
inputs change.
1. module tb;
2. reg a, b;
3. wire out1, out2;
4. des d0 (.out1(out1), .out2(out2), .a(a), .b(b));
5. initial begin
6. {a, b} <= 0;
7. $monitor ("T=%0t a=%0b b=%0b and=%0b bufif0=%0b", $time, a, b, out
1, out2);
8. #10 a <= 1;
9. #10 b <= 1;
10. #10 a <= 0;
11. #10 b <= 0;
12. end
13. endmodule
Output
ncsim> run
T=0 a=0 b=0 and=x bufif0=x
T=2 a=0 b=0 and=0 bufif0=x
T=3 a=0 b=0 and=0 bufif0=0
T=10 a=1 b=0 and=0 bufif0=0
T=13 a=1 b=0 and=0 bufif0=1
T=20 a=1 b=1 and=0 bufif0=1
T=22 a=1 b=1 and=1 bufif0=1
T=23 a=1 b=1 and=1 bufif0=z
T=30 a=0 b=1 and=1 bufif0=z
T=32 a=0 b=1 and=0 bufif0=z
T=40 a=0 b=0 and=0 bufif0=z
T=43 a=0 b=0 and=0 bufif0=0
ncsim: *W,RNQUIE: Simulation is complete.
1. ncsim> run
2. T=0 a=0 b=0 and=x bufif0=x
3. T=3 a=0 b=0 and=0 bufif0=x
4. T=5 a=0 b=0 and=0 bufif0=0
5. T=10 a=1 b=0 and=0 bufif0=0
6. T=14 a=1 b=0 and=0 bufif0=1
7. T=20 a=1 b=1 and=0 bufif0=1
8. T=22 a=1 b=1 and=1 bufif0=1
9. T=24 a=1 b=1 and=1 bufif0=z
10. T=30 a=0 b=1 and=1 bufif0=z
11. T=33 a=0 b=1 and=0 bufif0=z
12. T=40 a=0 b=0 and=0 bufif0=z
13. T=45 a=0 b=0 and=0 bufif0=0
14. ncsim: *W,RNQUIE: Simulation is complete.
1. ncsim> run
2. T=0 a=0 b=0 and=x bufif0=x
3. T=3 a=0 b=0 and=0 bufif0=x
4. T=6 a=0 b=0 and=0 bufif0=0
5. T=10 a=1 b=0 and=0 bufif0=0
6. T=15 a=1 b=0 and=0 bufif0=1
7. T=20 a=1 b=1 and=0 bufif0=1
8. T=22 a=1 b=1 and=1 bufif0=1
9. T=27 a=1 b=1 and=1 bufif0=z
10. T=30 a=0 b=1 and=1 bufif0=z
11. T=33 a=0 b=1 and=0 bufif0=z
12. T=40 a=0 b=0 and=0 bufif0=z
13. T=46 a=0 b=0 and=0 bufif0=0
14. ncsim: *W,RNQUIE: Simulation is complete.
Min, Typ, and Max Delays
Delays are neither the same in different parts of the fabricated chip nor the
same for different temperatures and other variations. So Verilog also
provides an extra level of control for each of the delay types mentioned
above.
Every digital gate and transistor cell has a minimum, typical, and maximum
delay specified based on process node and is typically provided by libraries
from fabrication foundry.
For rise, fall, and turn-off delays, the three values min, typ, and max can be
specified and stand for minimum, typical and maximum delays.
This is another level of delay control in Verilog. Only one of the min, typ, and
max values can be used in the entire simulation run.
The min value is the minimum delay value that the gate is expected to have.
The typ value is the typical delay value that the gate is expected to have.
The max value is the maximum delay value that the gate is expected to
have.
1. ncsim> run
2. T=0 a=0 b=0 and=x bufif0=x
3. T=4 a=0 b=0 and=0 bufif0=x
4. T=7 a=0 b=0 and=0 bufif0=0
5. T=10 a=1 b=0 and=0 bufif0=0
6. T=16 a=1 b=0 and=0 bufif0=1
7. T=20 a=1 b=1 and=0 bufif0=1
8. T=23 a=1 b=1 and=1 bufif0=1
9. T=28 a=1 b=1 and=1 bufif0=z
10. T=30 a=0 b=1 and=1 bufif0=z
11. T=34 a=0 b=1 and=0 bufif0=z
12. T=40 a=0 b=0 and=0 bufif0=z
13. T=47 a=0 b=0 and=0 bufif0=0
14. ncsim: *W,RNQUIE: Simulation is complete.