Dataflow Modeling: Continuous Assignments

Dataflow modeling provides a higher level of abstraction than structural (gate-level) modeling. Instead of describing a circuit by connecting individual gates, dataflow modeling describes it in terms of how data flows through the circuit and how it is processed. This style focuses on the function of the circuit rather than its specific gate implementation, making it a more efficient and intuitive way to design combinational logic.

The core of dataflow modeling is the continuous assignment, which uses the `assign` keyword. These assignments model how a value is continuously driven onto a net (like a `wire`), similar to how a physical wire in a circuit is always driven by the output of a gate.

Continuous Assignments (`assign`)

The `assign` statement is the fundamental construct in dataflow modeling. It is used to drive a value onto a net variable.

Key Characteristics:

  • Continuous Evaluation: An `assign` statement is always active. The expression on the right-hand side is continuously evaluated, and whenever any of its operands change, the result is immediately propagated to the net on the left-hand side.
  • Target Must Be a Net: The left-hand side of an `assign` statement must be a net data type, most commonly a `wire`. It cannot be a `reg` data type.
  • Concurrent Execution: All `assign` statements in a module execute concurrently (in parallel), reflecting the parallel nature of hardware.

Syntax

assign [delay] net_variable = expression;
  • net_variable is typically a `wire`.
  • expression is a combination of other signals and operators.
  • The optional delay is used for simulation purposes to model gate delays.

Practical Examples of Dataflow Modeling

Example 1: Simple Logic Gates

Here’s how you can model basic logic gates using a single `assign` statement for each output.

module basic_gates (
    input  wire a, b,
    output wire y_and, y_or, y_xor, y_nand, y_not_a
);

    assign y_and   = a & b;
    assign y_or    = a | b;
    assign y_xor   = a ^ b;
    assign y_nand  = ~(a & b);
    assign y_not_a = ~a;

endmodule

Example 2: 2-to-4 Decoder

A 2-to-4 decoder takes a 2-bit input and activates one of four output lines. We can model this with four separate `assign` statements.

module decoder_2_to_4 (
    input  wire [1:0] in,
    input  wire       en,
    output wire [3:0] out
);

    assign out[0] = en & ~in[1] & ~in[0];
    assign out[1] = en & ~in[1] &  in[0];
    assign out[2] = en &  in[1] & ~in[0];
    assign out[3] = en &  in[1] &  in[0];

endmodule

Example 3: 4-bit Full Adder

A 4-bit adder can be modeled concisely using dataflow assignments. The concatenation operator `{}` is used to combine the final carry-out with the 4-bit sum.

module full_adder_4bit (
    input  wire [3:0] a, b,
    input  wire       c_in,
    output wire [3:0] sum,
    output wire       c_out
);

    // An intermediate 5-bit wire to hold the full result
    wire [4:0] result;

    // The addition is performed in a single expression
    assign result = a + b + c_in;

    // The lower 4 bits are the sum, the 5th bit is the carry-out
    assign sum   = result[3:0];
    assign c_out = result[4];

    // Alternative one-line assignment using concatenation:
    // assign {c_out, sum} = a + b + c_in;

endmodule

 

Verilog Language Fundamentals

 

Introduction

Every language, whether spoken or for programming, has a fundamental set of rules and building blocks. Verilog is no different. Before we can design complex digital circuits, we must first master the basic syntax and components that form the foundation of the language. This chapter introduces the essential elements of Verilog, from the primary design unit—the module—to the data types that hold values and the operators that manipulate them. Understanding these core concepts is the first and most critical step toward becoming proficient in hardware description with Verilog.

Module Declaration and Instantiation

The module is the basic building block of any Verilog design. It is a self-contained unit that describes a component of a digital circuit. Think of a module as a black box with inputs and outputs, which encapsulates a specific function. A complex design is built by connecting these modules together in a hierarchy.

Module Declaration

A module is defined using the `module` and `endmodule` keywords. The declaration includes the module name and a list of its ports. There are two common styles for declaring ports:

Style 1: Non-ANSI (Verilog-1995)

In this older style, the port names are listed in the module definition, and their directions are declared separately within the module body.

module and_gate_non_ansi (y, a, b);
    output y;
    input  a;
    input  b;

    assign y = a & b;
endmodule

Style 2: ANSI (Verilog-2001)

This modern, more concise style allows the port direction and data type to be declared directly within the port list. This is the recommended style for new designs.

module and_gate_ansi (
    output wire y,
    input  wire a,
    input  wire b
);

    assign y = a & b;
endmodule

Module Instantiation

Once a module is defined, you can use it within another module. This process is called instantiation. It creates a unique copy of the module and allows you to connect its ports to signals in the higher-level module. There are two ways to connect the ports:

Connection by Position vs. Connection by Name

module top_module ( ... );
    ...
    // Method 1: Connection by Position (Order Matters!)
    // Prone to errors if the module definition changes.
    and_gate_ansi u1 (out, in1, in2);

    // Method 2: Connection by Name (Recommended)
    // Explicitly connects ports, making code robust and readable.
    and_gate_ansi u2 (
        .y(out),   // Connects port 'y' of the and_gate to the 'out' wire
        .a(in1),   // Connects port 'a' to 'in1'
        .b(in2)    // Connects port 'b' to 'in2'
    );
endmodule

Data Types

Verilog uses a 4-state logic system. Every bit can have one of four values:

  • 0: Logic zero, or a false condition.
  • 1: Logic one, or a true condition.
  • X: An unknown logic value. This can represent an uninitialized value or a conflict (e.g., two drivers driving a wire with different values).
  • Z: High-impedance state. This represents a disconnected or floating wire. It is crucial for modeling buses where multiple devices can drive the same line.

Nets vs. Variables

Verilog has two main groups of data types: **nets** and **variables**.

  • Nets (e.g., wire, tri): Represent physical connections between hardware elements. They do not store values themselves but must be continuously driven by something, like the output of a gate or an `assign` statement. If nothing is driving a wire, its value is `Z`.
  • Variables (e.g., reg, integer): Represent storage elements. They hold a value until a new value is assigned to them within a procedural block (like `always` or `initial`). If a `reg` is not assigned, it holds its previous value.
Note: The keyword reg does not always mean a hardware register (flip-flop). It simply means it is a variable that can store a value. Synthesis tools will infer a flip-flop only if the `reg` is assigned a value based on a clock edge within an `always` block.

Vectors and Parameters

Signals can be single-bit (scalar) or multi-bit (vector).

// Scalar (single-bit) signals
wire enable;
reg  error_flag;

// Vector (multi-bit) signals
wire [7:0] data_bus;  // An 8-bit wire from bit 7 down to bit 0
reg  [0:31] address; // A 32-bit register from bit 0 up to bit 31

// Parameters define constants to make code reusable
parameter DATA_WIDTH = 16;
wire [DATA_WIDTH-1:0] wide_bus; // A 16-bit wire

Operators

Verilog provides a rich set of operators to manipulate data.

Category Operators Description
Arithmetic + - * / % Addition, Subtraction, Multiplication, Division, Modulus.
Logical && || ! Logical AND, OR, NOT. Returns a single bit (1 or 0).
Bitwise & | ~ ^ ~^ Bitwise AND, OR, NOT, XOR, XNOR. Operates on each bit of the operands.
Relational > < >= <= == != Comparison operators.
Shift >> << >>> <<< Logical and Arithmetic shift operators.
Concatenation { } Joins bits of two or more signals together. {a, b}

Lexical Conventions and Comments

Verilog follows specific rules for naming and commenting.

  • Identifiers: Names for modules, variables, etc., must start with a letter or underscore and can contain letters, numbers, underscores, and dollar signs ($). Verilog is case-sensitive.
  • Comments: Use comments to make your code readable.
    • Single-line comments start with //.
    • Multi-line comments are enclosed in /* ... */.
  • Number Literals: You can specify the size and base of a number. The format is <size>'<base><value>.
    • 8'b1010_1111 // 8-bit binary number (underscores are ignored)
    • 12'hA2F // 12-bit hexadecimal number
    • 32'd255 // 32-bit decimal number