Chapter 2: Verilog Structure


Chapter 2: Verilog Structure

This chapter gives a quick overview of the structural and behavioral elements of Verilog and discusses the time flow and time control statements. Verilog differs from regular programming languages (C, Pascal, ...) in 3 main aspects: (1) simulation time concept, (2) multiple threads, and (3) some basic circuit concepts like network connections and primitive gates. If you know how to program in C and you understand basic digital design then learning Verilog will be easy.

If you have VeriLogger Pro installed on your computer you can copy and paste the following code segments into a text file and simulate the examples.

2.1 Modules

In Verilog, circuit components are designed inside a module. Modules can contain both structural and behavioral statements. Structural statements represent circuit components like logic gates, counters, and microprocessors. Behavioral level statements are programming statements that have no direct mapping to circuit components like loops, if-then statements, and stimulus vectors which are used to exercise a circuit.

Figure 1 shows an example of a circuit and a test bench module. A module starts with the keyword module followed by an optional module name and an optional port list. The key word endmodule ends a module.

`timescale 1ns / 1ps
//create a NAND gate out of an AND and an Invertor
module some_logic_component (c, a, b);
       // declare port signals
  output c;
  input a, b;
      // declare internal wire 
  wire d;
      //instantiate structural logic gates
  and a1(d, a, b); //d is output, a and b are inputs
  not n1(c, d);    //c is output, d is input

//test the NAND gate
module test_bench; //module with no ports
  reg A, B;
  wire C;

  //instantiate your circuit
  some_logic_component S1(C, A, B);

  //Behavioral code block generates stimulus to test circuit
      A = 1'b0; B = 1'b0; 
      #50 $display("A = %b, B = %b, Nand output C = %b \n", A, B, C);
      A = 1'b0; B = 1'b1;
      #50 $display("A = %b, B = %b, Nand output C = %b \n", A, B, C);
      A = 1'b1; B = 1'b0;
      #50 $display("A = %b, B = %b, Nand output C = %b \n", A, B, C);
      A = 1'b1; B = 1'b1;
      #50 $display("A = %b, B = %b, Nand output C = %b \n", A, B, C);

Figure 1 shows a simple logic circuit and test bench.

2.2 Structural Design with Gate Primitives and the Delay operator

Verilog defines some basic logic gates as part of the language. In Figure 1, module some_logic_component instantiates two gate primitives: the not gate and the and gate. The output of the gate is the first parameter, and the inputs are the rest of the parameters. These primitives are scaleable so you can get multiple input gates just by adding inputs into the parameter list. For example:

nand a1(out1, in1, in2);                //2-input NAND gate
nand a2(out1, in1, in2, in3, in4, in5); //5-input NAND gate

By default the timing delay for the gate primitives is zero time. You can define the rising delay, falling delay using the #(rise, fall) delay operator. And for tri-state gates you can also define the turn-off delay (transition to high impedance state Z) by using the #(rise, fall, off) delay operator. For example

notif0 #(10,11,27) inv2(c,d,control) //rise=10, fall=11, off=27(not if control=0)
nor    #(10,11)    nor1(c,a,b);  //rise=10, fall=11   (nor gate) 
xnor   #(10)      xnor1(i,g,h);  //rise=10, fall=10   (xnor gate)

Also each of the 3 delays can be defined to have minimum, typical, and a maximum value using the a colon to separate the values like 8:10:12 instead of 10 in the above examples. At run time, the Verilog simulator looks for to see if the +mindelay, +typdelay, or +maxdelay option has been defined so that it will know which of the 3 time values to use. In VeriLogger these options are set using the Project > Project Preferences menu. If none of the options are specified then the typical value is used.

// min:typ:max values defined for the (rise, fall) delays 
or  #(8:10:12, 10:11:13) or1(c,a,b);  

The delay operator has one subtle side effect: it swallows narrow input pulses. Normally, the delay operator causes the output response of a gate to be delayed a certain amount of time. However if the input pulse width is shorter then the overall delay of the gate then the change will not be shown on the output.

Here is a list of logic primitives defined for Verilog:

Gate Parameter List Examples
nand nor and or xor xnor scalable, requires at least 2 inputs(output, input1, input2, , inputx) and a1(C,A,B);nand na1(out1,in1,in2,in3,in4);nor #(5) n1(D,A,B);//delay = 5 time unitsxor #(3,4,5) x1(E,A,B);//rise,fall,off delaysnor #(3:4:5) n2(F,A,B);//min:typ:max of delays
not buf (output, input) not inv1(c,a);
notif0bufif0 control signal active low(output, input, control) notif0 inv2(c,a, control);
notif1bufif1 control signal active high(output, input, control) not inv1(c,a, control);

2.3 Structural Design with Assignment Statements

If you have a lot of random logic, the gate primitives of the previous section are tedious to use because all the internal wires must be declared and hooked up correctly. Sometimes it is easier to just describe a circuit using a single Boolean equation. In Verilog, Boolean equations which have similar timing properties as the gate primitives are defined using a continuous assignment statement.

For example, the following code excerpt from Figure 1:

wire d;
and a1(d, a, b); 
not n1(c, d);    

can be replaced with one statement:

assign c = !(a && b); //notice that wire d was not used here

Assignments can also be made during the declaration of a wire. In this case the assign keyword is implicitly assumed to be there for example:

wire d;
assign d = a || b; //continuous assignment

wire d = a || b;   //implicit continuous assignment

By default the timing delay for assignment statements is zero time. You can define a propagation delay using the #delay operator just like we did for the gate primitives. The following examples have the exact same timing.

wire c;
assign #5 c = a && b; //delay in the continuous assignment

wire #5 c = a && b;   //delay in the implicit assignment

wire #5 c;  //delay in the wire declaration
assign c = a && b;    

To demonstrate the pulse swallowing effect of the delays operator, consider the following senario. In the above examples, if input a changed value at time 10 (and held its value for at least 5 time units), then the output c would change values at time 15. If input a had a value pulse that was shorter then the propagation delay of the assignment then the value on a would not be passed to the output.

The delay operator can also use the full rise, fall, and off delays and each delay can have a minimum:typical: maximum value. The following is a valid line of code.

and #(8:10:12, 10:11:13, 26:27:29) a1(c,a,b); //min:typ:max of (rise,fall,off)

Appendix A defines all of the operators that can be used in an assignment statement.

2.4 Structural Design with using Modules

Verilog supports hierarchical design by allowing modules to instantiate other modules. For example in Figure1 module test_bench instantiates a component S1 of type some_logic_component. The code is reprinted here for convince:

module test_bench; 
  some_logic_component S1(C, A, B); //instantiate a some_logic_component module

By default the timing inside a module is controlled by the module itself. However, modules can be defined to have parameterized delays similar to the #(4,5) delay operator used with gate primitives. In the module definition, use the parameter keyword to create delay variables. Parameters can also be used to change other scalar values in the module. When the module is instantiated then you can choose to override the delay values using the #(parameter) notation. For example:

module some_logic_component (c, a, b);
    ... //some code
  parameter andDelay = 2;    //default delays
  parameter invDelay = 2;
  and #andDelay a1(d, a, b); //using parameter delays
  not #invDelay n1(c, d);    

module test_bench; //module with no ports
 some_logic_component #(5,4) S3(E, A, B); //override andDelay=5, invDelay=4
 some_logic_component #(5)   S2(D, A, B); //override andDelay=5, invDelay=2
 some_logic_component        S1(C, A, B); //uses default delays

Modules also support a special kind of timing called specify blocks which can be used in conjunction with SDF analyzers. Specify blocks also support continuous setup and hold checking.

2.5 Behavioral Design with Initial and Always blocks

Behavioral code is used to describe circuits at a more abstract level then the structural level statements we have studied. All Behavioral code occurs within either an initial block or in an always block. A module can contain several initial and always blocks. These behavioral blocks contain statements that control simulation time, data flow statements (like if-then and case statements), and blocking and non-blocking statements.

An initial block executes once during a simulation. Initial blocks are usually used to initialize variables and to describe stimulus waveforms which exercise which drive the simulation.

An always block continuously repeats its execution during a simulation. Always blocks usually contain behavioral code that models the actual circuit operation.

During a simulation each always and each initial block begin to execute at time zero. Each block executes concurrently with each structural statement and all the other behavioral blocks. The following example shows a behavioral SRAM model. The initial block sets the memory cells to zero at startup. The always block executes each time there is a change on the write control line, the chip select line, or the address bus. As an exercise, copy and paste this code into a verilog file and write a test bench to exercise the model. If you are using VeriLogger Pro then you can draw a test bench.

//SRAM Model
module sram(CSB,WRB,ABUS,DATABUS);
  input CSB;             // active low chip select
  input WRB;             // active low write control
  input [11:0] ABUS;     // 12-bit address bus
  inout [7:0] DATABUS;   // 8-bit data bus
                 //** internal signals
  reg  [7:0] DATABUS_driver;
  wire [7:0] DATABUS = DATABUS_driver;
  reg [7:0] ram[0:4095];            // memory cells
  integer i;

  initial     //initialize all RAM cells to 0 at startup
    DATABUS_driver = 8'bzzzzzzzz;
    for (i=0; i < 4095; i = i + 1)
       ram[i] = 0;

  always @(CSB or WRB or ABUS)
      if (CSB == 1'b0)
        if (WRB == 1'b0) //Start: latch Data on rising edge of CSB or WRB
          DATABUS_driver <= #10 8'bzzzzzzzz;
          @(posedge CSB or posedge WRB);
          $display($time," Writing %m ABUS=%b DATA=%b",ABUS,DATABUS);
          ram[ABUS] = DATABUS;
        if (WRB == 1'b1) //Reading from sram (data becomes valid after 10ns)
          #10 DATABUS_driver =  ram[ABUS];
          $display($time," Reading %m ABUS=%b DATA=%b",ABUS,DATABUS_driver);
      else //sram unselected, stop driving bus after 10ns
        DATABUS_driver <=  #10 8'bzzzzzzzz;



SynaptiCAD provides a free 6 month license for Verilog Simulator