Verilog also provides support for transistor level modeling although it is rarely used by designers these days as the complexity of circuits have required them to move to higher levels of abstractions rather than use switch level modeling.

NMOS/PMOS

  
  
module des (input d, ctrl,
			output outn, outp);
			
  nmos (outn, d, ctrl);
  pmos (outp, d, ctrl);
endmodule		

  
  
  
module tb;
  reg d, ctrl;
  wire outn, outp;
  
  des u0 (.d(d), .ctrl(ctrl), .outn(outn), .outp(outp));
  
  initial begin
    {d, ctrl} <= 0;
    
    $monitor ("T=%0t d=%0b ctrl=%0b outn=%0b outp=%0b", $time, d, ctrl, outn, outp);
    
    #10 d <= 1;
    #10 ctrl <= 1;
    #10 ctrl <= 0;
    #10 d <= 0;
  end
endmodule

  
Simulation Log
ncsim> run
T=0 d=0 ctrl=0 outn=z outp=0
T=10 d=1 ctrl=0 outn=z outp=1
T=20 d=1 ctrl=1 outn=1 outp=z
T=30 d=1 ctrl=0 outn=z outp=1
T=40 d=0 ctrl=0 outn=z outp=0
ncsim: *W,RNQUIE: Simulation is complete.

CMOS Switches

  
  
module des (input d, nctrl, pctrl,
			output out);
			
  cmos (out, d, nctrl, pctrl);
endmodule

  
  
  
module tb;
  reg d, nctrl, pctrl;
  wire out;
  
  des u0 (.d(d), .nctrl(nctrl), .pctrl(pctrl), .out(out));
  
  initial begin
    {d, nctrl, pctrl} <= 0;
    
    $monitor ("T=%0t d=%0b nctrl=%0b pctrl=%0b out=%0b", $time, d, nctrl, pctrl, out);
    
    #10 d <= 1;
    #10 nctrl <= 1;
    #10 pctrl <= 1;
    #10 nctrl <= 0;
    #10 pctrl <= 0;
    #10 d <= 0;
    #10;
  end
endmodule

  
Simulation Log
ncsim> run
T=0 d=0 nctrl=0 pctrl=0 out=0
T=10 d=1 nctrl=0 pctrl=0 out=1
T=20 d=1 nctrl=1 pctrl=0 out=1
T=30 d=1 nctrl=1 pctrl=1 out=1
T=40 d=1 nctrl=0 pctrl=1 out=z
T=50 d=1 nctrl=0 pctrl=0 out=1
T=60 d=0 nctrl=0 pctrl=0 out=0
ncsim: *W,RNQUIE: Simulation is complete.

Bidirectional Switches

tran

  
  
module des (input io1, ctrl,
            output io2);

  tran (io1, io2);	
endmodule

  
  
  
module tb;
  reg io1, ctrl;
  wire io2;
  
  des u0 (.io1(io1), .ctrl(ctrl), .io2(io2));
  
  initial begin
    {io1, ctrl} <= 0;
    
    $monitor ("T=%0t io1=%0b ctrl=%0b io2=%0b", $time, io1, ctrl, io2);
    
    #10 io1  <= 1;
    #10 ctrl <= 1;
    #10 ctrl <= 0;
    #10 io1  <= 0;
        
  end
endmodule

  
Simulation Log
ncsim> run
T=0 io1=0 ctrl=0 io2=0
T=10 io1=1 ctrl=0 io2=1
T=20 io1=1 ctrl=1 io2=1
T=30 io1=1 ctrl=0 io2=1
T=40 io1=0 ctrl=0 io2=0
ncsim: *W,RNQUIE: Simulation is complete.

tranif0

  
  
module des (input io1, ctrl,
            output io2);
            
  tranif0 (io1, io2, ctrl);
endmodule

  
Simulation Log
ncsim> run
T=0 io1=0 ctrl=0 io2=0
T=10 io1=1 ctrl=0 io2=1
T=20 io1=1 ctrl=1 io2=z
T=30 io1=1 ctrl=0 io2=1
T=40 io1=0 ctrl=0 io2=0
ncsim: *W,RNQUIE: Simulation is complete.

tranif1

  
  
module des (input io1, ctrl,
            output io2);
            
  tranif1 (io1, io2, ctrl);
endmodule

  
Simulation Log
ncsim> run
T=0 io1=0 ctrl=0 io2=z
T=10 io1=1 ctrl=0 io2=z
T=20 io1=1 ctrl=1 io2=1
T=30 io1=1 ctrl=0 io2=z
T=40 io1=0 ctrl=0 io2=z
ncsim: *W,RNQUIE: Simulation is complete.

Power and Ground

  
  
module des (output vdd, 
			output gnd);
			
	supply1 _vdd;
	supply0 _gnd;
	
	assign vdd = _vdd;
	assign gnd = _gnd;
endmodule						

  
  
  
module tb;
  wire vdd, gnd;
  
  des u0 (.vdd(vdd), .gnd(gnd));
  
  initial begin
    #10;
    $display ("T=%0t vdd=%0d gnd=%0d", $time, vdd, gnd);
  end
endmodule

  
Simulation Log
ncsim> run
T=10 vdd=1 gnd=0
ncsim: *W,RNQUIE: Simulation is complete.

Digital elements are binary entities and can only hold either of the two values - 0 and 1. However the transition from 0 to 1 and 1 to 0 have a transitional delay and so does each gate element to propagate 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 any of its inputs become 0. These gate and pin to pin delays can be specified in Verilog when instantiating logic primitives.

Rise, Fall and Turn-Off Delays

The time taken for the output of a gate to change from some value to 1 is called a rise delay. The time taken for the output of a gate to change fomr some value to 0 is called a fall delay. The time taken for the output of a gate to change from some value to high impedance is called turn-off delay.

These delays are actually applicable 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 and the two delay format can be applied to most primitives whose outputs do not transition to high impedance. Like a three delay format cannot be applied to an AND gate because the output will not go to Z for any input combination.

  
  
// Single delay specified - used for all three types of transition delays
or #(<delay>) o1 (out, a, b);

// Two delays specified - used for Rise and Fall transitions
or #(<rise>, <fall>) o1 (out, a, b);

// Three delays specified - used for Rise, Fall and Turn-off transitions
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 rise and the second one represents fall delay. If there are three delays specified, they represent rise, fall and turn-off delays respectively.

One Delay Format

  
  
module des (	input 	a, b,
            	output out1, out2);

	// AND gate has 2 time unit gate delay
  and 		#(2) o1 (out1, a, b);
  
  // BUFIF0 gate has 3 time unit gate delay
  bufif0 	#(3) b1 (out2, a, b);
  
endmodule

  
  
  
module tb;
  reg a, b;
  wire out1, out2;
  
  des d0 (.out1(out1), .out2(out2), .a(a), .b(b));
  
  initial begin
    {a, b} <= 0;
    
    $monitor ("T=%0t a=%0b b=%0b and=%0b bufif0=%0b", $time, a, b, out1, out2);
    
    #10 a <= 1;
    #10 b <= 1;
    #10 a <= 0;
    #10 b <= 0;
  end
endmodule

  

See that the output of AND gates change 2 time units after one of its inputs change. For example, b becomes 1 while a is already 1 at T=20. But the output becomes 1 only at T=22. Similarly, a goes back to zero at T=30 and the output gets the new value at T=32.

Gate delay is specified as 3 time units for BUFIF0 and hence when b changes from 0 to 1 while a is already at 1, output takes 3 time units to get updated to Z and finally does so at T=23.

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

Two Delay Format

Let's apply the same testbench shown above to a different Verilog model shown below where rise and fall delays are explicitly mentioned.

  
  
module des (	input 	a, b,
            	output out1, out2);

  and #(2, 3) o1 (out1, a, b);
  bufif0 #(4, 5) b1 (out2, a, b);
  
endmodule

  
Simulation Log
ncsim> run
T=0 a=0 b=0 and=x bufif0=x
T=3 a=0 b=0 and=0 bufif0=x
T=5 a=0 b=0 and=0 bufif0=0
T=10 a=1 b=0 and=0 bufif0=0
T=14 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=24 a=1 b=1 and=1 bufif0=z
T=30 a=0 b=1 and=1 bufif0=z
T=33 a=0 b=1 and=0 bufif0=z
T=40 a=0 b=0 and=0 bufif0=z
T=45 a=0 b=0 and=0 bufif0=0
ncsim: *W,RNQUIE: Simulation is complete.

Three Delay Format

  
  
module des (	input 	a, b,
            	output out1, out2);

  and #(2, 3) o1 (out1, a, b);
  bufif0 #(5, 6, 7) b1 (out2, a, b);
  
endmodule

  
Simulation Log
ncsim> run
T=0 a=0 b=0 and=x bufif0=x
T=3 a=0 b=0 and=0 bufif0=x
T=6 a=0 b=0 and=0 bufif0=0
T=10 a=1 b=0 and=0 bufif0=0
T=15 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=27 a=1 b=1 and=1 bufif0=z
T=30 a=0 b=1 and=1 bufif0=z
T=33 a=0 b=1 and=0 bufif0=z
T=40 a=0 b=0 and=0 bufif0=z
T=46 a=0 b=0 and=0 bufif0=0
ncsim: *W,RNQUIE: Simulation is complete.

Min/Typ/Max Delays

Delays are not the same in different parts of the fabricated chip nor is it 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 each type of delay - rise, fall, and turn-off - three values min, typ and max can be specified and stand for minimum, typical and maximum delays.

  
  
module des (	input 	a, b,
            	output out1, out2);

  and #(2:3:4, 3:4:5) o1 (out1, a, b);
  bufif0 #(5:6:7, 6:7:8, 7:8:9) b1 (out2, a, b);
  
endmodule

  
Simulation Log
ncsim> run
T=0 a=0 b=0 and=x bufif0=x
T=4 a=0 b=0 and=0 bufif0=x
T=7 a=0 b=0 and=0 bufif0=0
T=10 a=1 b=0 and=0 bufif0=0
T=16 a=1 b=0 and=0 bufif0=1
T=20 a=1 b=1 and=0 bufif0=1
T=23 a=1 b=1 and=1 bufif0=1
T=28 a=1 b=1 and=1 bufif0=z
T=30 a=0 b=1 and=1 bufif0=z
T=34 a=0 b=1 and=0 bufif0=z
T=40 a=0 b=0 and=0 bufif0=z
T=47 a=0 b=0 and=0 bufif0=0
ncsim: *W,RNQUIE: Simulation is complete.

A previous example explored a simple sequence detector. Here is another example for a pattern detector which detects a slightly longer pattern.

Design

  
  
module det_110101 ( input clk,
                  	 input rstn,
                  	 input in,
                  	 output out );
  
  parameter IDLE 	= 0,
  			S1 		= 1,
  			S11 	= 2,
  			S110 	= 3,
  			S1101 	= 4,
  			S11010 	= 5,
  			S110101 = 6;
  
  reg [2:0] cur_state, next_state;
  
  assign out = cur_state == S110101 ? 1 : 0;
  
  always @ (posedge clk) begin
    if (!rstn)
      	cur_state <= IDLE;
     else 
     	cur_state <= next_state;
  end
  
  always @ (cur_state or in) begin
    case (cur_state)
      IDLE : begin
        if (in) next_state = S1;
        else 	next_state = IDLE;
      end
      
      S1: begin
        if (in) next_state = S11;
        else 	next_state = IDLE;
      end

      S11: begin
        if (!in) next_state = S110;
        else 	next_state = S11;
      end
      
      S110 : begin
        if (in) next_state = S1101;
        else 	next_state = IDLE;
      end
      
      S1101 : begin
        if (!in) next_state = S11010;
        else 	next_state = IDLE;
      end
      
      S11010: begin
        if (in) next_state = S110101;
        else 	next_state = IDLE;
      end
      
      S110101: begin
        if (in) next_state = S1;
        else 	next_state = IDLE; 		// Bug 2
      end      
    endcase
  end
endmodule

  

Testbench

  
  
module tb;
  reg clk, in, rstn;
  wire out;
  integer l_dly;
  
  always #10 clk = ~clk;
  
  det_110101 u0 ( .clk(clk), .rstn(rstn), .in(in), .out(out) );
  
  initial begin
  	clk <= 0;
    rstn <= 0;
    in <= 0;
    
    repeat (5) @ (posedge clk);
    rstn <= 1;

    @(posedge clk) in <= 1;
    @(posedge clk) in <= 1;
    @(posedge clk) in <= 0;
    @(posedge clk) in <= 1;
    @(posedge clk) in <= 0;
    @(posedge clk) in <= 1;
    @(posedge clk) in <= 1;
    @(posedge clk) in <= 1;
    @(posedge clk) in <= 0;
    @(posedge clk) in <= 1;
    @(posedge clk) in <= 0;
    @(posedge clk) in <= 1;
    
    #100 $finish;
  end
endmodule

  
Simulation Log
ncsim> run
T=10 in=0 out=0
T=30 in=0 out=0
T=50 in=0 out=0
T=70 in=0 out=0
T=90 in=0 out=0
T=110 in=1 out=0
T=130 in=1 out=0
T=150 in=0 out=0
T=170 in=1 out=0
T=190 in=0 out=0
T=210 in=1 out=0
T=230 in=1 out=1
T=250 in=1 out=0
T=270 in=0 out=0
T=290 in=1 out=0
T=310 in=0 out=0
T=330 in=1 out=0
T=350 in=1 out=1
T=370 in=1 out=0
T=390 in=1 out=0
T=410 in=1 out=0
Simulation complete via $finish(1) at time 430 NS + 0

A very common example of an FSM is that of a sequence detector where the hardware design is expected to detect when a fixed pattern is seen in a stream of binary bits that are input to it.

Example

  
  
module det_1011 ( input clk,
                  input rstn,
                  input in,
                  output out );
  
  parameter IDLE 	= 0,
  			S1 		= 1,
  			S10 	= 2,
  			S101 	= 3,
  			S1011 	= 4;
  
  reg [2:0] cur_state, next_state;
  
  assign out = cur_state == S1011 ? 1 : 0;
  
  always @ (posedge clk) begin
    if (!rstn)
      	cur_state <= IDLE;
     else 
     	cur_state <= next_state;
  end
  
  always @ (cur_state or in) begin
    case (cur_state)
      IDLE : begin
        if (in) next_state = S1;
        else next_state = IDLE;
      end
      
      S1: begin
        if (in) next_state = IDLE;
        else 	next_state = S10;
      end
      
      S10 : begin
        if (in) next_state = S101;
        else 	next_state = IDLE;
      end
      
      S101 : begin
        if (in) next_state = S1011;
        else 	next_state = IDLE;
      end
      
      S1011: begin
        next_state = IDLE;
      end
    endcase
  end
endmodule

  

Testbench

  
  
module tb;
  reg 			clk, in, rstn;
  wire 			out;
  reg [1:0] l_dly;
  reg 			tb_in;
  integer 	loop = 1;
  
  always #10 clk = ~clk;
  
  det_1011 u0 ( .clk(clk), .rstn(rstn), .in(in), .out(out) );
  
  initial begin
  	clk <= 0;
    rstn <= 0;
    in <= 0;
    
    repeat (5) @ (posedge clk);
    rstn <= 1;

		// Generate a directed pattern
    @(posedge clk) in <= 1;
    @(posedge clk) in <= 0;
    @(posedge clk) in <= 1;
    @(posedge clk) in <= 1; 		// Pattern is completed
    @(posedge clk) in <= 0;
    @(posedge clk) in <= 0;
    @(posedge clk) in <= 1;
    @(posedge clk) in <= 1;
    @(posedge clk) in <= 0;
    @(posedge clk) in <= 1;
    @(posedge clk) in <= 1; 	 // Pattern completed again
    
    // Or random stimulus using a for loop that drives a random
    // value of input N times
    for (int i = 0 ; i < loop; i ++) begin
      l_dly = $random;
      repeat (l_dly) @ (posedge clk);
      tb_in = $random;
      in <= tb_in;
    end
    
    // Wait for sometime before quitting simulation
    #100 $finish;
  end
endmodule

  
Simulation Log
ncsim> run
T=10 in=0 out=0
T=30 in=0 out=0
T=50 in=0 out=0
T=70 in=0 out=0
T=90 in=0 out=0
T=110 in=1 out=0
T=130 in=0 out=0
T=150 in=1 out=0
T=170 in=1 out=0
T=190 in=0 out=1
T=210 in=0 out=0
T=230 in=1 out=0
T=250 in=1 out=0
T=270 in=0 out=0
T=290 in=1 out=0
T=310 in=1 out=0
T=330 in=1 out=1
T=350 in=1 out=0
T=370 in=1 out=0
T=390 in=1 out=0
Simulation complete via $finish(1) at time 410 NS + 0

There is a bug in the design. Can you find it ?

Verilog needs to represent individual bits as well as groups of bits. For example, a single bit sequential element is a flip-flop. However a 16-bit sequential element is a register that can hold 16 bits. For this purpose, Verilog has scalar and vector nets and variables.

Scalar and Vector

A net or reg declaration without a range specification is considered 1-bit wide and is a scalar. If a range is specified, then the net or reg becomes a multibit entity known as a vector.

scalar and vector in verilog
  
  
	wire 	    o_nor;           // single bit scalar net
	wire [7:0]  o_flop;          // 8-bit vector net
	reg         parity;          // single bit scalar variable
	reg  [31:0] addr;            // 32 bit vector variable to store address

  

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.

  
  
	wire  [msb:lsb]   name;
	integer           my_msb;
	
	wire [15:0]        priority;      // msb = 15, lsb = 0
	wire [my_msb: 2]   prior;         // illegal

  

A 16 bit wide net called priority will be created in the example above. Note that the msb and lsb should be a constant expression and cannot be substituted by a variable. But they can be any integer value - positive, negative or zero; and the lsb value can be greater than, equal to or less than msb value.

Bit-selects

Any bit in a vectored variable can be individually selected and assigned a new value as shown below. This is called as a bit-select. If the bit-select is out of bounds or the bit-select is x or z, then the value returned will be x.

bit-select in verilog
  
  
	reg [7:0]      addr;         // 8-bit reg variable [7, 6, 5, 4, 3, 2, 1, 0]
	
	addr [0] = 1;                // assign 1 to bit 0 of addr
	addr [3] = 0;                // assign 0 to bit 3 of addr
	addr [8] = 1;                // illegal : bit8  does not exist in addr

  

Part-selects

part-select in verilog

A range of contiguous bits can be selected and is known as a part-select. There are two types of part-selects, one with a constant part-select and another with an indexed part-select.

  
  
	reg [31:0]    addr;
	
	addr [23:16] = 8'h23;         // bits 23 to 16 will be replaced by the new value 'h23 -> constant part-select

  

Having a variable part-select allows it to be used effectively in loops to select parts of the vector. Although the starting bit can be varied, the width has to be constant.

[<start_bit> +: <width>]     // part-select increments from start-bit
[<start_bit> -: <width>]     // part-select decrements from start-bit
  
  
module des;
  reg [31:0]  data;
  int         i;
  
  initial begin
    data = 32'hFACE_CAFE;
    for (i = 0; i < 4; i++) begin
      $display ("data[8*%0d +: 8] = 0x%0h", i, data[8*i +: 8]);
    end
    
    $display ("data[7:0]   = 0x%0h", data[7:0]);
    $display ("data[15:8]  = 0x%0h", data[15:8]);
    $display ("data[23:16] = 0x%0h", data[23:16]);
    $display ("data[31:24] = 0x%0h", data[31:24]);
  end
  
endmodule

  
Simulation Log
ncsim> run
data[8*0 +: 8] = 0xfe              // ~ data [8*0+8 : 8*0]
data[8*1 +: 8] = 0xca              // ~ data [8*1+8 : 8*1]
data[8*2 +: 8] = 0xce              // ~ data [8*2+8 : 8*2]
data[8*3 +: 8] = 0xfa              // ~ data [8*3+8 : 8*3]

data[7:0]   = 0xfe
data[15:8]  = 0xca
data[23:16] = 0xce
data[31:24] = 0xfa
ncsim: *W,RNQUIE: Simulation is complete.

Common Errors

  
  
module tb;
   reg [15:0]    data;
   
   initial begin
      $display ("data[0:9] = 0x%0h", data[0:9]);   // Error : Reversed part-select index expression ordering
   end
endmodule