L03 Introduction to Verilog I

Dr Ryan Robucci

Objective

  1. Introduction to Verilog
  2. Verilog basics
  3. Testbenches
  4. Coding Style
  5. Extended Introduction

What is Verilog

Verilog (IEEE 1364) is a hardware description language (HDL) that can be used to model electric systems for simulation and hardware synthesis and verification.

Components of a modeling/description language

📝 concurrency

  • we can use the term concurrency to denotes the lack of control flow between statements that may be executed in an order other than specified or even at the same time (unlike sequential code that you are familiar with)
  • concurrent programming involves describing a set of operations for which the overall functionality may be independent of hte order of execution
    • if the functionality of the group of statements is defined/changes by the order of execution, then there is a non-determinism that may need to be addressed
  • concurrency in simulation is a way of modeling hardware that always operates in parallel

📝 parallelism

  • we can use parallism denotes the availability of hardware resources to perform operations at the same time
  • ex: a NOT gate connect to the output of an AND does not wait for the AND gate

Concurrency and Parallelism Reference: Patrick Schaumont, A Practical Introduction to Hardware/Software Codesign

Verilog basics

Verilog coding styles

Types of primitives

Built-in primitives provide a starting set of building blocks for describing digital logic.

Structural Verilog with Module and Testbench

module mux_structural(d0,d1,sel,y);

  input d0,d1;
  input sel;
  output y; //defaults to type wire, which is suitable for 
           //  making connections between parts in netlists
  wire sel_n,w0,w1;

  not i0 (sel_n,sel);
  and i1 (w0,d0,sel_n);
  and i2 (w1,d1,sel);
  or  i3 (y,w0,w1);
endmodule

Line 1

Line 4-6

Line 8

Line 9-12

Understand the concurrent description: at the base Verilog constructs are concurrent by default. In the example, the order of the gates provided in lines 8-11 do not matter

A testbench:

module tb();              //a testbench is typically self-contained and thus has no ports
                          //Internal Signals
  reg d0,d1,sel;          //reg:  signals that will be assigned within the same
                          //       hierarchy level using procedural code
  wire y;                 //wire: signals for connections
                          //In SystemVerilog, use: logic d0,d1,sel,y;
                          //Instantiation of module, referred to as Device Under Test
  mux_structural dut(.*); //SystemVerilog default connection syntax
                          // for standard Verilog use explicit port mapping
                          //   mux_structural dut(.d0(d0),.d1(d1),.sel(sel),.y(y));

  integer count;          //working integer variable
  
  initial begin           //produral code is typically used to generate input stimulus
    count = 0;            //   unless a test-jig module 
    forever #1 count++;   //   is instantiated to interact with the DUT
  end

  assign {d0,d1,sel}=count;

  initial #0 $display("d0 d1 sel  y"); // zero-delay #0 ensures that printing
                                       // after circuit initialization is completed 
  initial    $monitor("%2b ",d0,"%2b ",d1,"%2b ",sel," ","%2b ",y);

  initial #7 $finish;   //terminates simulation
  
endmodule

The highlighted code lines with the DUT instantiation, the assign statement, the run-once initial lines, and the block of code in lines 14-17 may be provided in any order since the base level of code is concurrent.

The lines within begin end statements, lines 15-16, are sequntial code and only there does order matter.

The $monitor is a printing task supporting multiple inputs, as well as formatting strings followed by the arguments satisfying inputs to the formatting string. The monitor task automatically re-triggers and prints when any mutable input changes.

Results: (iverilog ‘-Wall’ ‘-g2012’ design.sv testbench.sv && unbuffer vvp a.out)

d0 d1 sel  y
 0  0  0   0 
 0  0  1   0 
 0  1  0   0 
 0  1  1   1 
 1  0  0   1 
 1  0  1   0 
 1  1  0   1 
 1  1  1   1 

Another Example Using Multi-Bit Vectors:

module mux_structural_2bit(d0,d1,sel,y);
  input [1:0] d0,d1; //multi-bit input
  input sel;
  output [1:0] y; //multi-bit output
                  //  making connections between parts in netlists
  wire sel_n;
  wire [1:0] w0,w1; //multi-bit internal buses
  not i0 (sel_n,sel);

  and i1 (w0[0],d0[0],sel_n);
  and i2 (w1[0],d1[0],sel);
  or  i3 ( y[0],w0[0],w1[0]);

  and i4 (w0[1],d0[1],sel_n);
  and i5 (w1[1],d1[1],sel);
  or  i6 ( y[1],w0[1],w1[1]);
endmodule

module tb();
  
  reg [1:0] d0,d1;
  reg sel;
  wire [1:0] y; 
  mux_structural_2bit dut(.*);

  integer count;
  
  initial begin
    count = 0;
    forever #1 count++;
  end
   
  initial $display("%3s ","sel","%3s ","d1","%3s ","d0"," ","%3s ","y");
  initial $monitor("%3b ", sel ,"%3b ", d1 ,"%3b ", d0 ," ","%3b ", y );

  assign {sel,d1,d0}=count;

  initial #31 $finish;
  
endmodule
sel  d1  d0    y 
  0  00  00   00 
  0  00  01   01 
  0  00  10   10 
  0  00  11   11 
  0  01  00   00 
  0  01  01   01 
  0  01  10   10 
  0  01  11   11 
  0  10  00   00 
  0  10  01   01 
  0  10  10   10 
  0  10  11   11 
  0  11  00   00 
  0  11  01   01 
  0  11  10   10 
  0  11  11   11 
  1  00  00   00 
  1  00  01   00 
  1  00  10   00 
  1  00  11   00 
  1  01  00   01 
  1  01  01   01 
  1  01  10   01 
  1  01  11   01 
  1  10  00   10 
  1  10  01   10 
  1  10  10   10 
  1  10  11   10 
  1  11  00   11 
  1  11  01   11 
  1  11  10   11 
  1  11  11   11 

Dataflow modelling

Dataflow modeling example

Module:

module mux_dataflow_2bit_enables(d0,d1,sel,enCh0,enCh1,enGlobal,y);
  input [1:0] d0,d1; 
  input sel,enCh0,enCh1,enGlobal;
  output [1:0] y;
                  
  wire sel_n;
  wire w0,w1; 
  wire y0,y1;

  assign w0  = (d0[0] & ~sel) | (d1[0] & sel); //NOT, ANDs, OR
  assign w1  = sel ? d1[1] : d0[1];   // C-like conditional ternary operator
  assign y0 = w0 & enCh0 & enGlobal;  // string of binary (2-inputs) AND gates forms
                                      //    a three-input AND gate 
  assign y1 = & {w1,enCh1,enGlobal};  // reduction and compresses 3 bits to 
                                      //   one resulting bit result using the AND operator 
  assign y = {y1,y0};                 // concatenation operator
endmodule

Testbench:

module tb();
  
  reg [1:0] d0,d1;
  reg enCh0,enCh1,enGlobal;
  reg sel;
  wire [1:0] y; 
  mux_dataflow_2bit_enables dut(.*);

  integer count;
  
  initial begin
      count = 0;
      forever #1 count++;
  end
   
  initial $display("%8s ","enGlobal","%6s ","enCh1","%6s ","enCh0", "%3s " ,"sel","%3s ","d1","%3s ","d0"," ","| %3s","y");
  initial $monitor("%8b ", enGlobal ,"%6b ", enCh1 ,"%6b ", enCh0 , "%3b " , sel ,"%3b ", d1 ,"%3b ", d0 ," ","| %3b", y );

  assign {enGlobal,enCh1,enCh0,sel,d1,d0}=count;

  initial #255 $finish;
  
endmodule

Output:

enGlobal  enCh1  enCh0 sel  d1  d0  |   y
       0      0      0   0  00  00  |  00
       0      0      0   0  00  01  |  00
       0      0      0   0  00  10  |  00
       0      0      0   0  00  11  |  00
       0      0      0   0  01  00  |  00
...
       0      0      1   1  00  11  |  00
       0      0      1   1  01  00  |  00
...
       0      1      0   0  10  00  |  00
       0      1      0   0  10  01  |  00
       0      1      0   0  10  10  |  00
       0      1      0   0  10  11  |  00
       0      1      0   0  11  00  |  00
...
       1      0      1   1  01  11  |  01
       1      0      1   1  10  00  |  00
       1      0      1   1  10  01  |  00
...
       1      1      0   0  01  11  |  10
       1      1      0   0  10  00  |  00
       1      1      0   0  10  01  |  00
       1      1      0   0  10  10  |  10
       1      1      0   0  10  11  |  10
....
       1      1      1   1  10  01  |  10
       1      1      1   1  10  10  |  10
       1      1      1   1  10  11  |  10
       1      1      1   1  11  00  |  11
       1      1      1   1  11  01  |  11
       1      1      1   1  11  10  |  11
       1      1      1   1  11  11  |  11

Behavioral and RTL model

Behavioral and RTL model (Initial and Always Blocks)

Edge-Sensitive vs Value-Sensitive Functions

RTL Verilog example:

module mux_rtl_2bit_enables(d0,d1,sel,enCh0,enCh1,enGlobal,y);
  input [1:0] d0,d1; 
  input sel,enCh0,enCh1,enGlobal;
  output reg [1:0] y; 
                      
  wire sel_n;
  wire w0, w1; 

  always @ (d0,d1,sel,enCh0,enCh1,enGlobal) begin: blk0
    reg y1,y0;
    y1 = 1'bx;
    y0 = 1'bx;
    if (enGlobal) begin 
      case (sel)
        1'b0 : begin 
          {y1,y0} = { d0[1]&enCh1 , d0[0]&enCh0 };
            y = {y1,y0};
          end
        1'b1 : y = d1 & {enCh1,enCh0}; //bit-wise AND, mult-bit assignment
      endcase
    end else begin
        y = 2'b0;
    end
  end
endmodule

Testbench:

module tb();
  
  reg [1:0] d0,d1;
  reg enCh0,enCh1,enGlobal;
  reg sel;
  wire [1:0] y_rtl; 
  wire [1:0] y_df;
  mux_dataflow_2bit_enables dut_df (.y(y_df) ,.*);
  mux_rtl_2bit_enables      dut_rtl(.y(y_rtl),.*);

  integer count;
  
  initial begin
    count = 0;
    forever #1 count++;
  end
   
  wire [1:0] match = y_df~^y_rtl;

  initial $display("%8s ","enGlobal","%6s ","enCh1","%6s ","enCh0", "%3s " ,"sel","%3s ","d1","%3s ","d0"," ","| %6s","y_df"," %6s","y_rtl", "%6s" ,"match");
  initial $monitor("%8b ", enGlobal ,"%6b ", enCh1 ,"%6b ", enCh0 , "%3b " , sel ,"%3b ", d1 ,"%3b ", d0 ," ","| %6b", y_df ," %6b", y_rtl , "%6b" , match );

  assign {enGlobal,enCh1,enCh0,sel,d1,d0}=count;

  initial #255 $finish;
  
endmodule

Output:

enGlobal  enCh1  enCh0 sel  d1  d0  |   y_df  y_rtl match
       0      0      0   0  00  00  |     00     00    11
       0      0      0   0  00  01  |     00     00    11
       0      0      0   0  00  10  |     00     00    11
...
       1      1      1   1  11  10  |     11     11    11
       1      1      1   1  11  11  |     11     11    11
...

Sensitivity List

Behavioral Design with blocking and non-blocking assignment statements

There are 2 kinds of assignment statements:

reg vs. Register and the new SystemVerilog logic

reg≢registerreg \not\equiv register

When discussing a variable y declared using reg y; refer to y as “reg” “y”, not as “register” “y”. You can also refer to it as “variable” “y”.

Origin of the confusion: “Although the Verilog HDL is used for more than just simulation, the semantics of the language are defined for simulation, and everything else is abstracted from this base definition.” – page 64 of Verilog Standard

From a simulation perspective, a reg is assigned through procedural code and in the execution of that code the simulator might require a memory in order to compute the resulting value of a reg.

Implementing OR with a variable (reg) in simulation
The code below describes a signal y that is updated with the value a|b|c any time a,b,or c changes. Clearly this is a 3-input OR gate, which is combinatorial. However, the algorithm/code used to describe the function requires a memory of y to compute the resulting value of y. To a simulator, a type reg is internally modeled distinctly than a wire.

...
reg y;
always @(a,b,c) begin
y = a;
y = y|b;
y = y|c;
end
...

Implementing without variable in simulation
This code computes the result of the expression assigning a|b|c and after computing the result, independant of y, and schedules an assignment of that result to y.

wire y;

assign y = a|b|c;

A reg is used in procedural-code as variables, which may end up being implemented in a synthesized circuit using sequential logic but just represent the output net of combinatorial logic otherwise.

Wires on the other hand are for structural connections (nets/wires) between modules or outputs of combinatorial expressions.

There is a simple rule to decide if you should use a reg or a wire: if the signal is assigned from a procedural block of code or not. It is so straightforward, that in SystemVerilog you can just use the datatype logic and the signal type of reg or wire is effectively inferred from its use.

System Verilog Update:

Rules of Thumb for Beginners

Simulation

The stimulus (input)

Viewing Results, using a Value Change Dump (VCD) File

Creating a VCD in Verilog

🫣 Controls for Dump

IEEE Std 1364™-2005
https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=1620780

  • $dumpfile("filename.vcd")
  • $dumpvars; : dumps all variables
  • $dumpvars(1); : dumps all variables in the current scope
  • dumpvars(level, list)
    • $dumpvars (1, top) : dump vars in module top
    • $dumpvars (0, top) : dump vars in and below level top
    • $dumpvars (0, top.module1, top.module2.net1); dump vars in and below level top.module1 as well as net1 in module2
  • To record waveforms only during certain phases of simulation (such as post-initialization) use…
  • $dumpoff; disables dumping
  • $dumpon; enables dumping
    • Example code
      initial begin
      #10 $dumpvars( . . . );
      #200 $dumpoff;
      #800 $dumpon;
      #900 $dumpoff;
      end
      
  • $dumpall; create checkpoint with the value of all selected variables
  • $dumplimit ( filesize ) ; maximum size before dumping is disabled
  • $dumpflush : flushes output, useful before crashing code, or perhaps before external library calls that potentially read dump file

Examples:

module top(led2,led1,led0, rst, clk);
  input clk;
  input rst;

  ouput logic led2;
  ouput logic led1;
  ouput logic led0;

  always_ff @ (posedge rst,posedge clk)
    if (rst)
      begin
        led0 <= 1;
        led1 <= 0;
        led2 <= 0;
      end
    else
      begin
        led0 <= 0;
        led1 <= led0;
        led2 <= led2;
      end
endmodule
module top_tb;

  logic clk;
  logic rst;
  logic led2;
  logic led1;
  logic led0;

  top DUT(
    .led2(led2),
    .led1(led1),
    .led0(led0),
    .rst(rst),
    .clk(clk)
  );

  initial begin
    $dumpfile("results.vcd");
    $dumpvars;

    rst = 1; clk  = 0;
    #10;
    clk  = 1;
    #5;
    clk  = 0;
    #5;
    clk  = 1;
    #5;
    clk  = 0;
    #5;
    clk  = 1;
    #5;
    clk  = 0;
    #5;
    clk  = 1;
    #5;
    clk  = 0;
    #20;
    $finish;
  end
endmodule

Design Strategies

Possible Blueprint:

Various Port Declaration Styles

There are multiple ways to define the ports. It is not important right now to master all of these or is it nessisary to be familiar with them to begin design, but it is important to be aware of alternate syntax in case you encounter code using another style. (may show up in slides just becuase it is more compact for presentation)

🧐 Port Declaration Options

Verilog 2000 has New Port Declaration Options

Verilog 95 code:

module memory( read, write, data_in, addr, data_out);
input  read;
input  write;
input  [7:0] data_in;
input  [3:0] addr;
output  [7:0] data_out;

reg [7:0] data_out;

After the port list, port direction must be declared to be input, output, or inout as well as the width if more than one bit
Type declaration: type is by default a wire unless another type is provided

Verilog 2k with direction and data type listed:

module memory(
  input wire read,
  input wire write,
  input wire [7:0] data_in,
  input wire [3:0] addr,
  output reg [7:0] data_out
);

Verilog 2k with no type in port list

module memory(
  input  read,
  input  write,
  input  [7:0] data_in,
  input  [3:0] addr,
  output reg [7:0] data_out
);

ports are declared as wire by default, override with keyword reg keyword

However, there is a stylistic disadvantage to exposing type in port declaration. In the following two examples, the function is the same but the type for y must change.

Verilog 2000 – port y as reg

module dff2y(output reg qA,
             output reg qB,
             output reg y,
             input dA,
             input dB,
             input en_n, 
             input clk)

  always@(posedge clk)
    if (~en_n) begin
      qA <= dA;
      qB <= dB;
    end

  always @(qA,aB) begin
    y = 1’b0;
    if (a&b) begin
      y = 1;
    end
  end
end

Verilog 2000 – port y as wire

module dff2y(output reg qA,
             output reg qB,
             output wire y,
             input dA,
             input dB,
             input en_n, 
             input clk)

  always@(posedge clk)
    if (~en_n) begin
      qA <= dA;
      qB <= dB;
    end

    assign y = qA&qB;
end

y is not the output of a register though declared as reg along with qA and qB in the left module declaration It is arguable that such an internal coding implementation detail does not belong in the presentation of an “external” interface

Verilog 2000 – hiding reg while exposing port direction using an intermediate signal

module dff2y(  output qA,
               output qB,
               output y,
               input dA,
               input dB,
               input en_n, 
               input clk)

  reg qA_int,qB_int,y_int;
  
  assign qA = qB_int;
  assign qB= qA_int;
  assign y= y_int;

always@(posedge clk)
    if (~en_n) begin
      qA_int <= dA;
      qB_int <= dB;
    end

  always @(qA,aB) begin
    y_int = 1’b0;
    if (a&b) begin
      y_int = a&b;
    end
  end
end

Implicit and/vs. Explicit Port Mapping

Implicit port mapping uses the order of the ports in the definition to imply connections to local nets

Metalogic Values

Verilog Supports more than the two logic values 0/Low and 1/High. The following Metalogic Values are important to be aware of since they may be encountered unintentionally in simulation.

L03 Introduction to Verilog I

Verilog basics