Verilog Description Styles

Introduction to Description Styles

There are two different styles of description:

    1. Data Flow
    • Continuous assignment, using assignment statements.
    1. Behavioral
    • Procedural assignment, using procedural statements similar to a program in a high-level language.
      • Blocking
      • Non-blocking

Data Flow Style: Continuous Assignment (assign)

Characteristics of assign

  • Identified by the keyword assign.
  • Forms a static binding between:
    • The net being assigned on the left-hand side (LHS).
    • The expression on the right-hand side (RHS), which may consist of both net and register type variables.
  • The assignment is continuously active.
  • Almost exclusively used to model combinational circuits.
  • Some points to note:
    • A Verilog module can contain any number of assign statements.
    • Typically, the assign statements are followed by procedural descriptions.
    • The assign statements are used to model behavioral descriptions.

Modeling Combinational Logic with assign

Generating a MUX with a Variable Index

  • Point to note:
    • Whenever there is an array reference on the RHS with a variable index, a MUX is generated by the synthesis tool.
    • If the index is a constant, just a wire will be generated.
      • Example: assign out = data[2];
  • The use of a non-constant index in an expression on the RHS generates a MUX.
  • Example:
    module generate_MUX (data, select, out);
      input [15:0] data;
      input [3:0] select;
      output out;
    
      assign out = data[select];
    endmodule
    

Generating a MUX with the Conditional Operator

  • Point to note:
    • Whenever a conditional is encountered in the RHS of an expression, a 2-to-1 MUX is generated.
    • In the example below, since the variables a, b, and f are vectors, an array of 2-to-1 MUX-es are generated.
  • Example:
    module generate_set_of_MUX (a, b, f, sel);
      input [0:3] a, b;
      input sel;
      output [0:3] f;
    
      assign f = sel ? a : b;
    endmodule
    
  • Question: What hardware will be generated by the following?
    • assign f = (a==0) ? (c+d) : (c-d);
    • Answer: The expression generates an adder and a subtractor whose outputs feed into a 2x1 MUX. The MUX’s select line is controlled by the output of a comparator that checks if a==0.

Generating a Decoder with a Variable Index

  • A non-constant index in an expression on the LHS generates a decoder.
  • Point to note:
    • A constant index in the expression on the LHS will not generate a decoder.
      • Example: assign out[5] = in;
      • This will simply generate a wire connection.
    • As a rule of thumb, whenever the synthesis tool detects a variable index in the LHS, a decoder is generated.
  • Example:
    module generate_decoder (out, in, select);
      input in;
      input [0:1] select;
      output [0:3] out;
    
      assign out[select] = in;
    endmodule
    

Modeling Sequential Logic with assign

D-Type Latch

  • This is an example to describe a sequential logic element using an assign statement.
  • Example:
    module level_sensitive_latch (D, Q, En);
      input D, En;
      output Q;
    
      assign Q = En ? D : Q;
    endmodule
    
  • Truth Table:
    • En | D | Qn
    • 0 | x | Qn-1
    • 1 | 0 | 0
    • 1 | 1 | 1

S-R Latch

  • Modeling a simple S-R latch. The circuit consists of two cross-coupled NAND gates.
  • Example:
    module sr_latch (Q, Qbar, S, R);
      input S, R;
      output Q, Qbar;
    
      assign Q = ~(R & Qbar);
      assign Qbar = ~(S & Q);
    endmodule
    
  • Testbench and Simulation:
    • A testbench can be created to apply stimulus to the S and R inputs.
    • Simulation Output:
      0 S=0, R=1, Q=0, Qbar=1
      5 S=1, R=1, Q=0, Qbar=1
      10 S=1, R=0, Q=1, Qbar=0
      15 S=1, R=1, Q=1, Qbar=0
      20 S=0, R=0, Q=1, Qbar=1
      and then the simulator hangs
      

Behavioral Style: Procedural Assignment

Procedural Blocks (initial and always)

  • Two kinds of procedural blocks are supported in Verilog:
    • The initial block
      • Executed once at the beginning of simulation.
      • Used only in test benches; cannot be used in synthesis.
    • The always block
      • A continuous loop that never terminates.
  • The procedural block defines:
    • A region of code containing sequential statements.
    • The statements execute in the order they are written.

The initial Block

  • All statements inside an initial statement constitute an initial block.
  • Statements are grouped inside a begin...end structure for multiple statements.
  • The statements start at time 0, and execute only once.
  • If there are multiple initial blocks, all the blocks will start to execute concurrently at time 0.
  • The initial block is typically used to write test benches for simulation:
    • Specifies the stimulus to be applied to the design-under-test (DUT).
    • Specifies how the DUT outputs are to be displayed / handled.
    • Specifies the file where the waveform information is to be dumped.

The always Block

  • All behavioral statements inside an always statement constitute an always block.
  • Multiple statements are grouped using begin...end.
  • An always statement starts at time 0 and executes the statements inside the block repeatedly, and never stops.
  • It is used to model a block of activity that is repeated indefinitely in a digital circuit.
  • Basic syntax of always block:
    always @(event_expression)
      begin
        sequential_statement_1;
        sequential_statement_2;
        ...
        sequential_statement_n;
      end
    
  • A module can contain any number of always blocks, all of which execute concurrently.
  • The @(event_expression) part is required for both combinational and sequential circuit descriptions.

Rules and Statements within Procedural Blocks

Sequential Statements

  • In Verilog, one or more sequential statements can be present inside an initial or always block.
    • The statements are executed sequentially.
    • Multiple assignment statements inside a begin...end block may either execute sequentially or concurrently depending upon on the type of assignment.
  • Two types of assignment statements: blocking (a = b + c;) or non-blocking (a <= b + c;).

Variable Assignment Rules

  • Only reg type variable can be assigned within an initial or always block.
  • Basic reason:
    • The sequential always block executes only when the event expression triggers.
    • At other times the block is doing nothing.
    • An object being assigned to must therefore remember the last value assigned (not continuously driven).
    • So, only reg type variables can be assigned within the always block.
  • Any kind of variable may appear in the event expression (reg, wire, etc.).

Sequential Control Statements

  • if ... else: Conditional branching with optional else if and else clauses.
  • case: Multiway branching (case, casez, casex). Can replace a complex if...else structure. The expression is compared to alternatives in order. A default statement can be used.
  • while loop: Executes a statement while an expression is true.
  • for loop: Consists of an initial condition, a terminating condition check, and a procedural assignment to change the control variable.
  • repeat loop: Executes a loop a fixed number of times. The expression is evaluated only once at the start.
  • forever loop: Executes forever until $finish is encountered. Equivalent to a while loop for which the expression is always true.

Timing and Event Control

  • # (time_value): Makes a block suspend for time_value units of time.
  • @ (event_expression): Makes a block suspend until event_expression triggers.
  • The event can be any one of the following:
    • a) Change of a signal value.
    • b) Positive or negative edge occurring on signal (posedge or negedge).
    • c) List of above-mentioned events, separated by or or comma.
  • A posedge is any transition from {0, x, z} to 1, and from 0 to {z, x}.
  • A negedge is any transition from {1, x, z} to 0, and from 1 to {z, x}.
  • Examples of event expressions:
    • @ (in): in changes.
    • @ (a or b or c) or @ (a, b, c): any of a, b, c changes.
    • @ (posedge clk): positive edge of clk.
    • @ (posedge clk or negedge reset): positive edge of clk or negative edge of reset.
    • @ (*): any variable changes.

Latch Inference in Combinational Logic

  • When a case statement is incompletely decoded, the synthesis tool will infer the need for a latch to hold the residual output when the select bits take the unspecified values.
  • This also applies to an if statement without a final else clause.
  • It is up to the designer to code the design in such a way that latch can be avoided where possible.
  • To avoid latch inference, either provide a default assignment in the branching statement or assign a default value to the variable at the start of the procedural block.

Examples of Procedural Assignments

  • Up-down Counter (synchronous clear):
    module counter (mode, clr, ld, d_in, clk, count);
      input mode, clr, ld, clk;
      input [0:7] d_in;
      output reg [0:7] count;
    
      always @(posedge clk)
        if (ld) count <= d_in;
        else if (clr) count <= 0;
        else if (mode) count <= count + 1;
        else count <= count - 1;
    endmodule
    
  • Parameterized Design: An N-bit Counter:
    • Use the keyword parameter to make a design general for any number of bits.
    • Parameter values are substituted before simulation or synthesis.
    module counter (clear, clock, count);
      parameter N = 7;
      input clear, clock;
      output reg [0:N] count;
    
      always @(negedge clock)
        if (clear)
          count <= 0;
        else
          count <= count + 1;
    endmodule
    
  • Using more than one clock in a module:
    module multiple_clk (clk1, clk2, a, b, c, f1, f2);
      input clk1, clk2, a, b, c;
      output reg f1, f2;
    
      always @(posedge clk1)
        f1 <= a & b;
      always @(negedge clk2)
        f2 <= b ^ c;
    endmodule
    
  • Using multiple edges of the same clock:
    module multi_edge_clk (a, b, c, d, f, clk);
      input clk;
      input [7:0] a,b,c,d;
      output reg [7:0] f;
    
      always @(posedge clk)
        c <= a + b;
      always @(negedge clk)
        f <= c - d;
    endmodule
    
    • Two operations are carried out every clock cycle. c is assigned at the rising edge, f is assigned at the falling edge.

Blocking vs. Non-Blocking Assignments

Introduction

  • We shall illustrate some examples that show how the modeling style influences the simulator or synthesizer to capture the behavior of the modeled circuit.
  • This is a very important concept required to be clearly understood by the designer.
  • Even a slight error in modeling can result in a drastically different circuit.
  • Highly recommended: For any confusion, write a Verilog code, simulate it and analyze the output(s).

Procedural Assignment Types

  • Procedural assignment statements can be used to update variables of types reg, integer, real, or time.
  • The value assigned to a variable remains unchanged until another procedural assignment statement assigns a new value.
  • This is different from continuous assignment (using assign) that results in the expression on the RHS to continuously drive the net type variable on the left.
  • Two types of procedural assignment statements:
    • a) Blocking (denoted by =)
    • b) Non-blocking (denoted by <=)
  • The left-hand side can be a register type variable, a bit select, a part select, or a concatenation.
  • The right-hand side can be any expression that evaluates to a value.
  • Procedural assignments can only appear within procedural blocks (initial or always).

Blocking Assignment (=)

  • General syntax: variable_name = [delay_or_event_control] expression;
  • The = operator is used to specify blocking assignment.
  • Blocking assignment statements are executed in the order they are specified in a procedural block.
  • The target of an assignment gets updated before the next sequential statement in the block is executed.
  • They do not block execution of statements in other procedural blocks.
  • This is the recommended style for modeling combinational logic.

Non-Blocking Assignment (<=)

  • General syntax: variable_name <= [delay_or_event_control] expression;
  • The <= operator is used to specify non-blocking assignment.
  • Non-blocking assignment statements allow scheduling of assignments without blocking execution of statements that follow within the procedural block.
  • The assignment to the target gets scheduled for the end of the simulation cycle.
  • Statements subsequent to the instruction under consideration are not blocked by the assignment.
  • Allows concurrent procedural assignment, suitable for sequential logic.

Modeling Guidelines and Rules

  • It is recommended that blocking and non-blocking assignments are not mixed in the same always block. This is not good design practice.
  • Verilog synthesizer ignores the delays specified in a procedural assignment statement. This may lead to functional mismatch between the design model and the synthesized netlist.
  • A variable cannot appear as the target of both a blocking and a non-blocking assignment.

Comparative Examples

Swapping Values

  • Trying to swap using blocking assignment:
    • always @(posedge clk) begin a=b; b=a; end
    • This is incorrect. Both a and b will be getting the value previously stored in b.
  • Swapping values of ‘a’ and ‘b’:
    • With Blocking assignments: always @(posedge clk) a=b; always @(posedge clk) b=a;
      • Either a=b will execute before b=a, or vice versa, depending on simulator implementation. Both registers will get the same value. This is a race condition.
    • With Non-blocking assignments: always @(posedge clk) a<=b; always @(posedge clk) b<=a;
      • Here the variables are correctly swapped. All RHS variables are read first, and assigned to LHS variables at the positive clock edge.

Shift Register Modeling

  • Example 3:
    • Correct blocking version: always @(posedge clock) begin q2 = q1; q1 = a; end
    • Correct non-blocking version: always @(posedge clock) begin q1 <= a; q2 <= q1; end
  • Example 4 (Correct): Using separate non-blocking always blocks correctly models a shift register.
    always @(posedge clock)
      q2 <= q1;
    always @(posedge clock)
      q1 <= a;
    
  • Example 5 (Incorrect): Using separate blocking always blocks creates a race condition, and the output q2 will be indeterminate.
    always @(posedge clock)
      q1 = a;
    always @(posedge clock)
      q2 = q1;
    
  • Example 6 (Correct): Modeling a 4-bit shift register with blocking assignments requires careful ordering.
    // Correct order for blocking
    always @(posedge clock or negedge clear)
      begin
        if (!clear) ...
        else begin
          E = D;
          D = C;
          C = B;
          B = A;
        end
      end
    
  • Example 6a (Incorrect): Reversing the order of blocking assignments is incorrect.
    // Incorrect order for blocking
    else begin
      B = A;
      C = B;
      D = C;
      E = D;
    end
    
    • The effect of the assignment B=A is immediate. The updated value is used in C=B, and so on. The four statements are equivalent to a single statement that assigns A to E.
  • Example 6b (Correct): This is the recommended style for modeling sequential circuits, using non-blocking assignments.
    • The statements can appear in any order. The chances of errors are less.
    • The right-hand side expressions are evaluated in parallel, so that order of the statements is not important.
    always @(posedge clock or negedge clear)
      begin
        if (!clear) ...
        else begin
          E <= D;
          D <= C;
          C <= B;
          B <= A;
        end
      end
    

Ring Counter Modeling

  • A ring counter is a circular shift register.
  • Incorrect with Blocking assignments:
    // Incorrect code
    else begin
      count = count << 1;
      count[0] = count[7];
    end
    
    • This solution is wrong. count[7] will get overwritten in the first statement. Rotation of the bits will not happen.
  • Correct with Non-blocking assignments (Modified version 1):
    // Correct code
    else begin
      count <= count << 1;
      count[0] <= count[7];
    end
    
    • Since non-blocking assignments are used, rotation will take place correctly.
  • Correct with Blocking assignment (Modified version 2):
    // Correct code using concatenation
    else
      count = {count[6:0], count[7]};
    
    • This is a correct way of modeling using blocking assignment.

Generate Blocks

Introduction to generate

  • generate statements allow Verilog code to be generated dynamically before the simulation or synthesis begins.
  • Very convenient to create parameterized module descriptions. Example: N-bit ripple carry adder for arbitrary value of N.
  • Requires the keywords generate and endgenerate.
  • Generate instantiations can be carried out for various Verilog blocks: Modules, user-defined primitives, gates, continuous assignments, initial and always blocks, etc.
  • Generated instances have unique identifier names and can be referenced hierarchically.

genvar Variables

  • Special genvar variables:
    • The keyword genvar can be used to declare variables that are used only in the evaluation of a generate block.
    • These variables do not exist during simulation or synthesis.
    • The value of a genvar can be defined only in a generate loop.
    • Every generate loop is assigned a name, so that variables inside the generate loop can be referenced hierarchically.

Examples of Generate Blocks

Example 1: Design of a Parameterized Bitwise XOR

module xor_bitwise (f, a, b);
  parameter N = 16;
  output [N-1:0] f;
  input [N-1:0] a, b;
  genvar p;

  generate
    for (p=0; p<N; p=p+1)
      begin: xorlp
        xor XG (f[p], a[p], b[p]);
      end
  endgenerate
endmodule
  • In the bitwise xor example, the name xorlp was given to the generate loop.
  • The relative hierarchical names of the xor gates will be: xorlp[0].XG, xorlp[1].XG, ..., xorlp[15].XG.

Example 2: Design of N-bit Ripple Carry Adder

  • We can use generate to dynamically create N copies of a full adder and connect them to make an N-bit ripple-carry adder.
  • Full Adder Module:
    module full_adder (a, b, c, sum, cout);
      input a, b, c;
      output, sum, cout;
      wire t1, t2, t3;
    
      xor G1 (t1, a, b);
      xor G2 (sum, t1, c);
      and G3 (t2, a, b);
      and G4 (t3, t1, c);
      or G5 (cout, t2, t3);
    endmodule
    
  • RCA Module using generate:
    module RCA (carry_out, sum, a, b, carry_in);
      parameter N = 8;
      input [N-1:0] a, b;
      input carry_in;
      output [N-1:0] sum;
      output carry_out;
      wire [N:0] carry;
    
      assign carry[0] = carry_in;
      assign carry_out = carry[N];
    
      genvar i;
      generate
        for (i=0; i<N; i++)
          begin: fa_loop
            // This implicitly instantiates a full adder
            wire t1, t2, t3;
            xor G1 (t1, a[i], b[i]);
            xor G2 (sum[i], t1, carry[i]);
            and G3 (t2, a[i], b[i]);
            and G4 (t3, t1, carry[i]);
            or G5 (carry[i+1], t2, t3);
          end
      endgenerate
    endmodule
    
  • Some of the relative hierarchical instance names that are generated are: fa_loop[0].G1, fa_loop[1].G1, fa_loop[7].G1, etc.
  • Some of the nets (wires) that are generated are: fa_loop[0].t1, fa_loop[1].t2, fa_loop[0].t3, etc.