Gradual Introduction to Verilog Syntax: Combinational Circuits

E.R.Doering

August 9, 2001

 

 

Key to typestyle: Italics => Verilog keyword; Bold => new concept introduced

 

Visualize the Hardware:

Describe the Hardware:

Comments:

“Do Nothing” circuit

module Gadget;

endmodule

A minimal Verilog description. The system has no I/O connections or internal functionality.

Verilog is case-sensitive throughout. Keywords must appear in lower case.

“Do Nothing” with I/O

module Gadget (a,b,c);



// Port modes
input a,b;
output c;

endmodule

I/O port names

Valid Verilog names include alphanumerics, underscore ‘_’, and dollar sign ‘$. Verilog names can be quite long.

Describe the “mode” (input or output) of each port.

Has no internal functionality.

Two-input NAND gate based on assign keyword

module Gadget (a,b,c);

/* Port modes */
input a,b;
output c;

// Functionality
assign c = ~(a & b);

endmodule



Alternative comment style



Describe a NAND gate using two “bitwise” operators.

NOTE: The “assign” technique is called a “continuous assignment.”

 

Bitwise Operators:
~     NOT
&     AND
|     OR
^     EXOR

Bitwise operators work on a bit-by-bit basis; for example:

“1 & 1” is 1
“01 | 10” is 11 (binary)

Two-Input NAND gate based on always keyword

module Gadget (a,b,c);

// Port modes
input a,b;
output c;

// Registered identifiers
reg c;







// Functionality
always @ (a or b)
      c <= ~(a & b);

endmodule







“Registered identifiers” can generate a logic value. The concept of a registered identifier is broader than simply an array of flip-flops; combinational circuit outputs must also be declared as registered identifiers when the “always” method (“procedural assignment”) is used.


Describes a combinational circuit using the procedural assignment technique. The “always” keyword means the circuit always (continuously) watches the signals in the input list ( the “@ (a or b)” part) and responds to changes in those signals by immediately updating the output identifier.

Use the “<=” symbol to assign an output inside an “always” block.

Two-input NAND and EXOR

module Gadget (a,b,c,d);

// Port modes
input a,b;
output c;
output d;

// Registered identifiers
reg c,d;

// Functionality
always @ (a or b) begin
      c <= ~(a & b);
      d <= a ^ b;
end

endmodule





Can have multiple “input” and “output” declarations





Can enclose multiple output signal assignments inside a “begin” - “end” block; everything between “begin” and “end” is treated as a single statement.

Two-input MUX

module Mux2 (
      A,    // A input
      B,    // B input
      Sel,  // Selector
      Y     // Output
);


// Port modes
input A,B,Sel;
output Y;

// Registered identifiers
reg Y;

// Functionality
always @ (A or B or Sel)
      if (Sel==0)
            Y <= A;
      else
            Y <= B;

endmodule


Demonstrates ability to embed comments inside the code













Use “if-else” technique to connect output “Y” to one of the two data inputs based on the value of the data selector “Sel”.

The expression inside the parentheses after “if” must evaluate to either “1” (true) or “0” (false).

 

Relational Operators:
==    Equal to
!=    Not equal
<     Less than
>     Greater than
<=    Less than or equal
>=    Greater than or equal
&&    AND
||    OR

Relational operators evaluate the comparison and return either true (1) or false (0):

In the previous example, “Sel==0” evaluates to “1” if “Sel” is zero, otherwise the comparison evaluates to “0”.

 

More Operators:
>>    Shift right
<<    Shift left
+     Add
-     Subtract
*     Multiply
/     Divide
%     Modulus






NOTE: Multiplication and division are not supported by standard hardware synthesis tools

Two-input MUX: Alternative method

// Functionality
always @ (A or B or Sel)
      if (Sel)
            Y <= B;
      else
            Y <= A;

 

Since “Sel” is a single-bit control signal, the expression inside the “(...)” test can be written more concisely.

Two-input MUX: Another alternative method

// Functionality
always @ (A or B or Sel)
      Y <= (Sel) ? B : A;

Here the ternary (three-part) operator is used. The “if-else” test is embedded into the assignment. Read the statement like this: “Is the control signal ‘Sel’ equal to 1? If yes, then use ‘B’ when making the assignment to ‘Y’, otherwise use ‘A’ ”.

Four-input MUX

module Mux4 (
      Data, // Data input
      Sel,  // Selector
      Y     // Output
);

// Port modes
input [3:0] Data;
input [1:0] Sel;
output Y;

// Registered identifiers
reg Y;

// Functionality
always @ (Data or Sel)
      if (Sel == 0)
            Y <= Data[0];
      else if (Sel == 1)
            Y <= Data[1];
      else if (Sel == 2)
            Y <= Data[2];
      else Y <= Data[3];


endmodule


Bus widths are not included in the port list.





A four-bit bus. The square brackets define the bus width. The left-side number is the MSB.

A two-bit bus is used for the data selector.





“if-else-if” technique


Method for referring to individual bus signals

Four-Input MUX: Alternative method

// Functionality
always @ (Data or Sel)
      case (Sel)
      0: Y <= Data[0];
      1: Y <= Data[1];
      2: Y <= Data[2];
      3: Y <= Data[3];
      default: Y <= Data[0];
      endcase



“case” technique (similar to “switch” statement in C language). First match between the case labels (left of colon) and the signal inside the parentheses causes the associated assignment to be made.

“default” keyword is a catch-all – if a match is not found, the default assignment is made.

Use “begin-end” blocks to do multiple assignments for a given case selector statement.

16-Input MUX with custom behavior

module Mux16 (
      Data, // Data input
      Sel,  // Selector
      Y     // Output
);

// Port modes
input [15:0] Data;
input [3:0] Sel;
output Y;

// Registered identifiers
reg Y;

// Functionality
always @ (Data or Sel)
      casez (Sel)
      4’b0000: Y <= Data[0];
      4’b0001: Y <= Data[1];






      4’b01??: Y <= Data[2];





      default: Y <= Data[3];
      endcase

endmodule


















Fully-specified constant – the “4” numerical value says the constant is 4 bits wide, and the ‘b says the digits are expressed in binary (use ‘d for decimal, ‘h for hexadecimal, and ‘o for octal). Underscores can be used to improve readability of long constants: 7’b010_0010

The question mark is a “don’t care” when it appears inside a numerical constant. In this example, the case selection ignores the two lowest bits and looks for a match on the two highest bits. You need to use the ‘casez’ version of the ‘case’ statement when using “don’t cares”.

All other values of the “Sel” control signal cause the fourth data bit to be selected.

Data bits 15 through 4 are effectively not used in this example.

Code Translator (specified as a truth table)

module Code_Translator (
      Code_In,
      Code_Out,
);

// Port modes
input [2:0] Code_In;
output [2:0] Code_Out;
 
// Registered identifiers
reg [2:0] Code_Out;

// Functionality
always @ (Code_In)
  case (Code_In)
  3’b000: Code_Out <= 3’b101;
  3’b001: Code_Out <= 3’b111;
  3’b010: Code_Out <= 3’b001;
  3’b011: Code_Out <= 3’b000;
  3’b100: Code_Out <= 3’b100;
  3’b101: Code_Out <= 3’b010;
  3’b110: Code_Out <= 3’b110;
  3’b111: Code_Out <= 3’b011;
  endcase

endmodule















The “case” statement can be used to directly implement a truth-table specification.






The “default” keyword is optional – however, you can get unexpected results if the preceding cases do not cover all possible input combinations.

Miscellaneous Techniques

{a,b}

{Data[13:12],a,b,Data[11:0]}



16{a}


assign Y = (en) ? X : 1’bz;

Group signals together with curly braces

Complex grouping – in this example, two signals are inserted between existing bus signals to form a new bus.

Replicates the single-bit signal ‘a’ into a 16-bit bus (each bus bit value is the same as ‘a’).

Tristate output control. When the enable (‘en’) is active, the output gets the signal ‘X’, otherwise the output is in high-impedance state.

4-bit magnitude comparator

module Compare4 (
      A,    // Data input A
      B,    // Data input B
      AltB, // A is less than B
      AeqB, // A is equal to B
      AgtB  // A is > than B
);

// Port modes
input [3:0] A,B;
output AltB,AeqB,AgtB;

// Registered identifiers
reg AltB,AeqB,AgtB;

// Functionality
always @ (A or B) begin
      AltB <= (A < B);
      AeqB <= (A == B);
      AgtB <= (A > B);
end

endmodule


















Since the relational operator evaluates to either a “1” or a “0”, the result of the comparison can be directly assigned to the single-bit result.

Parameterized design for n-bit comparator

module Compare4 (
      A,    // Data input A
      B,    // Data input B
      AltB, // A is less than B
      AeqB, // A is equal to B
      AgtB  // A is > than B
);

// Define bus width
parameter BusWidth = 8;

// Port modes
input [BusWidth-1:0] A,B;
output AltB,AeqB,AgtB;

// Registered identifiers
reg AltB,AeqB,AgtB;

// Functionality
always @ (A or B) begin
      AltB <= (A < B);
      AeqB <= (A == B);
      AgtB <= (A > B);
end

endmodule










Define bus width (use “8” in this example).


Make all subsequent references to bus width using the parameter. This way entire design can be adjusted simply by changing the original parameter statement.

Parameterized design (alternative method)

`define BusWidth = 8;

This technique uses a the “define” compiler directive. All compiler directives begin with the backwards apostrophe. The advantage of this method over the “parameter” method is that CAD tools will allow you to specify the value of your “define” directive outside your Verilog modules, obviating the need to edit the Verilog files themselves. This is particularly valuable if you want to deliver parameterizable designs to another designer without that designer having to edit the files.