Signals of type wire or a similar wire like data type requires the continuous assignment of a value. For example, consider an electrical wire used to connect pieces on a breadboard. As long as the +5V battery is applied to one end of the wire, the component connected to the other end of the wire will get the required voltage.

breadboard-circuit

In Verilog, this concept is realized by the an assign statement where any wire or other similar wire like 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.

Assign Syntax

The assignment syntax starts with the keyword assign followed by the signal name which can be either a single signal or a concatenation of different signal nets. The drive strength and delay are optional and are mostly used for dataflow modeling than synthesizing into real hardware. The expression or signal on the right hand side is evaluated and assigned to the net or expression of nets on the left hand side.

  
  
assign <net_expression> = [drive_strength] [delay] <expression of different signals or constant value>

  

Delay values are useful for specifying delays for gates and are used to model timing behavior in real hardware because the value dictates when the net should be assigned with the evaluated value.

Rules

There are some rules that need to be followed when using an assign statement:

  • LHS should always be a scalar or vector net or a concatenation of scalar or vector nets and never a scalar or vector register.
  • RHS can contain scalar or vector registers and function calls.
  • Whenever any operand on the RHS changes in value, LHS will be updated with the new value.
  • assign statements are also called continuous assignments and are always active

Example #1

In the following example, a net called out is driven continuously by an expression of signals. i1 and i2 with the logical AND & form the expression.

assign-flash-1

If the wires are instead converted into ports and synthesized, we will get an RTL schematic like the one shown below after synthesis.

Continuous assignment statement can be used to represent combinational gates in Verilog.

Example #2

The module shown below takes two inputs and uses an assign statement to drive the output z using part-select and multiple bit concatenations. Treat each case as the only code in the module, else many assign statements on the same signal will definitely make the output become X.

  
  
module xyz (input [3:0] 	x,		// x is a 4-bit vector net
						input 				y, 		// y is a scalar net (1-bit)
						output [4:0] 	z ); 	// z is a 5-bit vector net

wire [1:0] 	a;
wire 				b;
						
// Assume one of the following assignments are chosen in real design
// If x='hC and y='h1 let us see the value of z  

// Case #1: 4-bits of x and 1 bit of y is concatenated to get a 5-bit net
// and is assigned to the 5-bit nets of z. So value of z='b11001 or z='h19
assign z = {x, y};

// Case #2: 4-bits of x and 1 bit of y is concatenated to get a 5-bit net
// and is assigned to selected 3-bits of net z. Remaining 2 bits of z remains
// undriven and will be high-imp. So value of z='bZ001Z
assign z[3:1] = {x, y};

// Case #3: The same statement is used but now bit4 of z is driven with a constant
// value of 1. Now z = 'b1001Z because only bit0 remains undriven
assign z[3:1] = {x, y};
assign z[4] = 1;

// Case #4: Assume bit3 is driven instead, but now there are two drivers for bit3,
// and both are driving the same value of 0. So there should be no contention and 
// value of z = 'bZ001Z
assign z[3:1] = {x, y};
assign z[3] = 0;

// Case #5: Assume bit3 is instead driven with value 1, so now there are two drivers
// with different values, where the first line is driven with the value of X which
// at the time is 0 and the second assignment where it is driven with value 1, so
// now it becomes unknown which will win. So z='bZX01Z
assign z[3:1] = {x, y};
assign z[3] = 1;

// Case #6: Partial selection of operands on RHS is also possible and say only 2-bits
// are chosen from x, then z = 'b00001 because z[4:3] will be driven with 0
assign z = {x[1:0], y};

// Case #7: Say we explicitly assign only 3-bits of z and leave remaining unconnected
// then z = 'bZZ001
assign z[2:0] = {x[1:0], y};

// Case #8: Same variable can be used multiple times as well and z = 'b00111
// 3{y} is the same as {y, y, y}
assign z = {3{y}};

// Case #9: LHS can also be concatenated: a is 2-bit vector and b is scalar
// RHS is evaluated to 11001 and LHS is 3-bit wide so first 3 bits from LSB of RHS
// will be assigned to LHS. So a = 'b00 and b ='b1
assign {a, b} = {x, y};

// Case #10: If we reverse order on LHS keeping RHS same, we get a = 'b01 and b='b0
assign {a, b} = {x, y};

endmodule

  

Assign reg variables

It is illegal to drive or assign reg type variables with an assign statement. This is because a reg variable is capable of storing data and does not require to be driven continuously. reg signals can only be driven in procedural blocks like initial and always.

Implicit Continuous Assignment

When an assign statement is used to assign the given net with some value, it is called explicit assignment. Verilog also allows an assignment to be done when the net is declared and is called implicit assignment.

  
  
wire [1:0] a;
assign a = x & y; 			// Explicit assignment

wire [1:0] a = x & y; 	// Implicit assignment

  

Combinational Logic Design

Consider the following digital circuit made from combinational gates and the corresponding Verilog code.

combinational-gates

Combinational logic requires the inputs to be continuously driven to maintain the output unlike sequential elements like flip flops where the value is captured and stored at the edge of a clock. So an assign statement fits the purpose the well because the output o is updated whenever any of the inputs on the right hand side change.

  
  
// This module takes four inputs and performs a boolean
// operation and assigns output to o. The combinational
// logic is realized using assign statement.

module combo (	input 	a, b, c, d,
								output  o);
  
  assign o = ~((a & b) | c ^ d);
  
endmodule

  

Hardware Schematic

After design elaboration and synthesis, we do get to see a combinational circuit that would behave the same way as modeled by the assign statement.

combinational gate schematic

See that the signal o becomes 1 whenever the combinational expression on the RHS becomes true. Similarly o becomes 0 when RHS is false. Output o is X from 0ns to 10ns because inputs are X during the same time.

combo-gates-wave

Click here for a slideshow with simulation example !

An always block is one of the procedural blocks in Verilog. Statements inside an always block are executed sequentially.

Syntax

  
  
always @ (event)
	[statement]
	
always @ (event) begin
	[multiple statements]
end

  

The always block is executed at some particular event. The event is defined by a sensitivity list.

What is the sensitivity list ?

A sensitivity list is the expression that defines when the always block should be executed and is specified after the @ operator within parentheses ( ). This list may contain either one or a group of signals whose value change will execute the always block.

In the code shown below, all statements inside the always block get executed whenever the value of signals a or b change.

  
  
// Execute always block whenever value of "a" or "b" change
always @ (a or b) begin
	[statements]
end

  

What is the always block used for ?

An always block can be used to realize combinational or sequential elements. A sequential element like flip flop becomes active when it is provided with a clock and reset. Similarly, a combinational block becomes active when one of its input values change. These hardware blocks are all working concurrently independent of each other. The connection between each is what determines the flow of data. To model this behavior, an always block is made as a continuous process that gets triggered and performs some action when a signal within the sensitivity list becomes active.

In the following example, all statements within the always block get executed at every positive edge of the signal clk.

  
  
// Execute always block at positive edge of signal "clk"
always @ (posedge clk) begin
	[statements]
end

  

What happens if there is no sensitivity list ?

The always block repeats continuously throughout the duration of a simulation. The sensitivity list brings along a certain sense of timing i.e. whenever any signal in the sensitivity list changes, the always block is triggered. If there are no timing control statments within an always block, the simulation will hang because of a zero-delay infinite loop !

Example

The example shown below is an always block that attempts to invert the value of the signal clk. The statement is executed after every 0 time units. Hence, it executes forever because of the absence of a delay in the statement.

  
  
// always block is started at time 0 units
// But when is it supposed to be repeated ?
// There is no time control, and hence it will stay and
// be repeated at 0 time units only. This continues
// in a loop and simulation will hang !
always clk = ~clk;

  

Even 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 as shown below. Now, the clock inversion is done after every 10 time units.

  
  
	always #10 clk = ~clk;

  

Note: Explicit delays are not synthesizable into logic gates !

Hence real Verilog design code always require a sensitivity list.

Sequential Element Design Example

The code shown below defines a module called tff that accepts a data input, clock and active-low reset. The output gets inverted whenever d is found to be 1 at the positive edge of clock. Here, the always block is triggered either at the positive edge of clk or the negative edge of rstn.

What happens at the positive edge of clock ?

The following events happen at the positive edge of clock and is repeated for all positive edge of clock.

  1. First if block checks value of active-low reset rstn
    1. If rstn is zero, then output q should be reset to default value of 0
    2. If rstn is one, then it means reset is not applied and should follow default behavior
  2. If the previous step is false:
    1. Check value of d and if it is found to be one, then invert value of q
    2. If d is 0, then maintain value of q

  
  
module tff (input  		d,
						clk,
						rstn,
			output reg 	q);
			
	always @ (posedge clk or negedge rstn) begin
		if (!rstn)
			q <= 0;
		else
			if (d)
				q <= ~q;
			else
				q <= q;
	end
endmodule

  

What happens at the negative edge of reset ?

The following events happen at negative edge of rstn and happen at all such occurrences.

  1. First if block checks value of active-low reset rstn. At negative edge of the signal, its value is 0.
    1. If value of rstn is 0, then it means reset is applied and output should be reset to default value of 0
    2. The case where value of rstn is 1 is not considered because the current event is negative edge of the rstn

Combinational Element Design Example

An always block can also be used in the design of combinational blocks. For example the following digital circuit represents a combination of three different logic gates that provide a certain output at signal o.

combinational-gates

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. 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.

  
  
module combo (	input 	a,
      			input	b,
              	input	c,
              	input	d,
  	            output reg o);
  
  always @ (a or b or c or d) begin
    o <= ~((a & b) | (c^d));
  end
  
endmodule

  

See that the signal o becomes 1 whenever the combinational expression on the RHS becomes true. Similarly o becomes 0 when RHS is false.

Simulation Output combo-gates-wave

Click here for a slideshow with simulation example !

A set of Verilog statements are usually executed sequentially in a simulation. These statements are placed inside a procedural block. There are mainly two types of procedural blocks in Verilog - initial and always

Syntax

  
  
	initial 
		[single statement]

	initial begin
		[multiple statements]
	end

  

What is the initial block used for ?

An initial block is not synthesizable and hence cannot be converted into a hardware schematic with digital elements. Hence initial blocks do not serve much purpose than to be used in simulations. These blocks are primarily used to initialize variables and drive design ports with specific values.

When does an initial block start and end ?

An initial block is started at the beginning of a simulation at time 0 unit. This block will be executed only once during the entire simulation. Execution of an initial block finishes once all the statements within the block are executed.

verilog-initial-block

The image shown above has a module called behave which has two internal signals called a and b. 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.

What happens if there is a delay element ?

The code shown below has an additional statement that assigns some value to the signal b. However this happens only after 10 time units from execution of previous statement. This means that a is assigned first with the given value and then after 10 time units, b is assigned to 0.

verilog-initial-block-begin-end

How many initial blocks are allowed in a module ?

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.

verilog-multiple-initial-blocks

In this example, 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.

$finish is a Verilog system task that tells the simulator to terminate the current simulation.

If the last block had a delay of 30 time units like shown below, the simulation would have ended at 30 time units thereby killing all the other initial blocks that are active at that time.

  
  
	initial begin
		#30 $finish;
	end

  

Check out the flash example shown below to see how an initial block is executed in a simulation.

Click here for a slideshow with simulation example !

A digital element such as a flip-flop can be represented with combinational gates like NAND and NOR. The functionality of a flip-flop is achieved by the connection of a certain set of gates in a particular manner. How the gates have to be connected is usually figured out by solving K-map from the truth table. The truth table is nothing but a table that tells us what inputs combine together to give what values of output. Shown in the image below is an electronic circuit that represents a D-flip flop and the corresponding truth table. The output q becomes 1 only when rstn and d are both having a value of 1.

intro-verilog-hardware-schematic-to-verilog

What is a hardware schematic ?

A hardware schematic is a diagram that shows how the combinational gates should be connected to achieve a particular hardware functionality. In this case, it is the set of NAND gates connected like shown towards the left in the image above. However, if we know what values of inputs contribute to make the output have a value of 1, then we can essentially hide the internal details of the connections and encapsulate it into a black-box. This block provides us with certain inputs and outputs that is similar to the hardware schematic made up of combinational gates.

What is a Hardware Description Language ?

It will be easier if we can describe how this block should behave and then let software tools convert that behavior into actual hardware schematic. The language that describes hardware functionality is called Verilog, and is classified as a Hardware Description Language.

What is meant by design functionality ?

Some typical behavioral requirements for a D-flip-flop are :

  • clock should be an input to the flop
  • if the active-low reset is 0, then the flop should reset
  • if the active-low reset is 1, then the flop output 'q' should follow input 'd'
  • output 'q' should get a new value only at the posedge of clock

An important question comes to mind : how do we know whether the behavior described in Verilog accurately reflects the intended behavior of the design ?

What is verification ?

This is checked by different methods and is collectively called as verification. The most common and widely practiced method of verification is circuit simulation. There are software tools to understand how a hardware described in Verilog should behave and provide various input stimuli to the design model. The output of the design is then checked against expected values to see if the design is functionally correct.

All simulations are performed by EDA (Electronic Design Automation) software tools and the Verilog design RTL is placed inside an entity called as testbench. Within the testbench, various tests provide different stimuli to the design. Such a testbench is shown in the image below.

intro-verilog-testbench

Sections of Verilog code

All behavior code should be described within the keywords module and endmodule. Rest of the design code would mostly follow the given template.

Verilog sections template

  1. Module definition and port list declaration
  2. List of input and output ports
  3. Declaration of other signals using allowed Verilog data types
  4. Design may depend on other Verilog modules and hence their instances are created by module instantiations
  5. The actual Verilog design for this module that describes its behavior

  
  
module [design_name] ( [port_list] );

	[list_of_input_ports]
	[list_of_output_ports]

	[declaration_of_other_signals]

	[other_module_instantiations_if_required]

	[behavioral_code_for_this_module]
endmodule

  

Example

The code shown below describes the behavior of a D type flip-flop. The first few lines declare a new module called dff and define the input and output ports. The only other signal used in this design is q and is declared next. Since this is a simple design, it does not depend on any other module and hence there are no module instantiations. The always block describes how the hardware should behave during certain events and hence is behavioral code.

  
  
// "dff" is the name of this module 

module  dff  ( 	input 	d, 			// Inputs to the design should start with "input"
						rstn,
						clk,
				output	q); 		// Outputs of the design should start with "output"
				
	reg q; 							// Declare a variable to store output values
	
	always @ (posedge clk) begin 	// This block is executed at the positive edge of clk 0->1
		if (!rstn)  				// At the posedge, if rstn is 0 then q should get 0
			q <= 0;
		else 
			q <= d; 				// At the posedge, if rstn is 1 then q should get d
	end
endmodule 							// End of module

  

Testbench code

The testbench is the Verilog container module that allows us to drive the design with different inputs and monitor its outputs for expected behavior. In the example shown below, we have instantiated the flop design illustrated above and connected it with testbench signals denoted by tb_*. These testbench signals are then assigned certain values and are eventually driven as inputs to the design.

  
  
module tb;

	// 1. Declare input/output variables to drive to the design
	reg 	tb_clk;
	reg 	tb_d;
	reg 	tb_rstn;
	wire 	tb_q;
	
	// 2. Create an instance of the design
	// This is called design instantiation
	dff 	dff0 ( 	.clk 	(tb_clk), 		// Connect clock input with TB signal
					.d 		(tb_d), 		// Connect data input with TB signal
					.rstn 	(tb_rstn), 		// Connect reset input with TB signal
					.q 		(tb_q)); 		// Connect output q with TB signal
					
	// 3. The following is an example of a stimulus
	// Here we drive the signals tb_* with certain values
	// Since these tb_* signals are connected to the design inputs,
	// the design will be driven with the values in tb_*
	initial begin
		tb_rsnt 	<= 	1'b0;
		tb_clk 		<= 	1'b0;
		tb_d 		<=  1'b0;
	end
endmodule

  

Click here for a slideshow with simulation example !

A previous article showed different examples of using an always block to implement combinational logic. An always block is also mainly used to implement sequential logic which has memory elements like flip flops that can hold values.

JK Flip Flop

A JK flip flop is one of the many types of flops used to store values and has two data inputs j and k along with one for reset rstn and another for clock clk. The truth table for a JK flop is shown below and is typically implemented using NAND gates.

rstn j k q Comments
0 0 0 0 When reset is asserted, output is always zero
1 0 0 Hold value When both j and k are 0, output remains the same as before
1 0 1 1 When k=1, output becomes 1
1 1 0 0 When k=0, output becomes 0
1 1 1 Toggle value When j=1,k=1 output toggles current value

The behavioral Verilog code for a JK flip-flop can be written as shown below

  
  
module jk_ff ( input 			j, 				// Input J
               input 			k, 				// Input K
               input 			rstn, 		// Active-low async reset
               input 			clk, 			// Input clk
               output reg q); 			// Output Q

	always @ (posedge clk or negedge rstn) begin
		if (!rstn) begin
			q <= 0;
		end else begin
	  	q <= (j & ~q) | (~k & q);
	  end
  end
endmodule

  

Testbench

First declare all variables used in the testbench and start a clock using a simple always block that can be driven to the design. Then instantiate the design and connect its ports with corresponding testbench variables. Note that q is of type wire because it is connected to an output of the design which will be actively driving it. All other inputs to the design are of type reg so that they can be driven within a procedural block such as initial.

The stimulus first initializes all inputs to the design to zero and then de-asserts reset after some time. A for loop is used to drive different values to j and k which are driven at random times. Once the loop is done, wait for some more time and finish the simulation.

  
  
module tb;
	// Declare testbench variables
	reg j, k, rstn, clk;
	wire q;
	integer i;
	reg [2:0] dly;
	
	// Start the clock 
	always #10 clk = ~clk;
	
	// Instantiate the design
	jk_ff 	u0 (	.j(j), .k(k), .clk(clk), .rstn(rstn), .q(q));
	
	// Write the stimulus
	initial begin
		{j, k, rstn, clk} <= 0;
		#10 rstn <= 1;
		
		for (i = 0; i < 10; i = i+1) begin
			dly = $random;
			#(dly) j <= $random;
			#(dly) k <= $random;
		end
		
		#20 $finish;
	end
endmodule

  

Note from the simulation wave that at the posedge of clock, output q changes value based on the state of inputs j and k as given in the truth table.

Modulo-10 counter

Modulus(MOD) counters simply count upto a certain number before rolling back to zero. A MOD-N counter will count from 0 to N-1 and then roll back to zero and start counting again. Such counters typically require log2N number of flops to hold the count value. Shown below is the Verilog code for a MOD-10 counter that keeps counting up at every clock clk as long as reset rstn is deasserted.

Verilog parameters can be used to make a MOD-N counter which is more scalable.

  
  
module mod10_counter ( 	input		clk,
												input 	rstn,
												output	reg[3:0] out);
												
	always @ (posedge clk) begin
		if (!rstn) begin
			out <= 0;
		end else begin
			if (out == 10) 
				out <= 0;
			else
				out <= out + 1;
		end
	end
endmodule

  

Testbench

The testbench first declares some variables that can be assigned some values and driven to the design inputs. The counter module is then instantiated and connected with the testbench signals which are later driven with some values in the stimulus. Since the counter also requires a clock, the testbench clock is modeled with an always block. The stimulus simply sets default values at time 0ns, then deasserts reset after 10ns and the design is allowed to run for some time.

  
  
module tb;
	reg clk, rstn;
	reg [3:0] out;
	
	mod10_counter u0 ( .clk(clk), .rstn(rstn), .out(out));
	
	always #10 clk = ~clk;
	
	initial begin
		{clk, rstn} <= 0;
		
		#10 rstn <= 1;
		#450 $finish;
	end
endmodule

  

See that the counter module counts from zero to 9, rolls over to zero and starts counting again.

4bit Left Shift Register

Shown below is a 4-bit left shift register that accepts an input d into LSB and all other bits will be shifted left by 1. For example, if d equals zero and the initial value of the register is 0011, it will become 0110 at the next edge of the clock clk.

  
  
module lshift_4b_reg (  input d,                      
                        input clk,                    
                        input rstn,                   
                        output reg [3:0] out
                     );
 
   always @ (posedge clk) begin
      if (!rstn) begin
         out <= 0;
      end else begin
         out <= {out[2:0], d};
      end
   end
endmodule

  

Testbench

The testbench follows a similar template like the one shown before where some variables are declared, design module is instantiated and connected with the testbench signals. Then a clock is started and the stimulus is driven to the design using an initial block. In this testbench example, different values of d has to be exercised and hence a for loop is used to iterate 20 times and apply random values to the design.

  
  
module tb;
	reg clk, rstn, d;
	wire [3:0] out;
  integer i;
	
  lshift_4b_reg u0 (  .d(d), .clk(clk), .rstn(rstn), .out(out));
	
	always #10 clk = ~clk;
	
	initial begin
    {clk, rstn, d} <= 0;
    
    #10 rstn <= 1;
	
    for (i = 0; i < 20; i=i+1) begin
      @(posedge clk) d <= $random; 
    end
    
    #10 $finish;
	end  
endmodule

  

Note that each bit is shifted to the left by 1 and the new value of d is applied to LSB.