In programming, scope determines where variables, functions, and methods can be accessed or modified. It helps to avoid naming conflicts by creating separate namespaces for each part of a program. In Verilog, scope plays a similar role, creating a namespace for modules, functions, tasks, and other blocks. Verilog defines a new scope for each module, function, task, named block, and generate block. This article will show you how Verilog hierarchical reference scope works with examples and explanations.

Scope in Verilog: A Basic Example

Consider this simple Verilog code:

module tb;
    reg signal;

    // Error: You cannot declare the same variable 'signal' again in the same scope
    reg signal;

    // You can reuse 'signal' inside a task because it's in a different scope
    task display();
        reg signal = 1;
        $display("signal = %0b", signal);
    endtask
endmodule

In the example above:

  • You cannot declare the same variable twice in the same scope. Here, signal is declared twice in the tb module, which results in an error.
  • However, the name signal can be reused inside the task display because tasks have their own scope.

Scope Rules in Verilog

  • An identifier (e.g., signal) can only be declared once within a specific scope.
  • You cannot have two variables with the same name, even if they have different types.
  • Tasks, variables, nets, and gate instances must not share the same name in the same scope.

Hierarchical References in Verilog

Verilog allows you to use hierarchical references to access items in different scopes. Each module, task, or block defines a new level in the hierarchy, and you can reference elements in these higher or lower levels using hierarchical paths.

Example: Using Verilog hierarchical reference scope

Here’s an example demonstrating hierarchical references:

module tb;
    // Create instances of two different modules
    A uA();
    B uB();

    // Create a named block within the tb module
    initial begin : TB_INITIAL
        reg signal;
        #10 $display("signal = %0d", signal);
    end

    // Accessing other scopes using hierarchical references
    initial begin
        TB_INITIAL.signal = 0;  // Access signal in TB_INITIAL block
        uA.display();  // Call task in module A
        uB.B_INITIAL.B_INITIAL_BLOCK1.b_signal_1 = 1;  // Access signal in module B
        uB.B_INITIAL.B_INITIAL_BLOCK2.b_signal_2 = 0;  // Access another signal in module B
    end
endmodule

module A;
    task display();
        $display("Hello, this is A");
    endtask
endmodule

module B;
    initial begin : B_INITIAL
        #50;
        begin : B_INITIAL_BLOCK1
            reg b_signal_1;
            #10 $display("signal_1 = %0d", b_signal_1);
        end

        #50;
        begin : B_INITIAL_BLOCK2
            reg b_signal_2;
            #10 $display("signal_2 = %0d", b_signal_2);
        end
    end
endmodule

Simulation Log Output:

xcelium> run
Hello, this is A
TB signal = 0
signal_1 = 1
signal_2 = 0
xmsim: *W,RNQUIE: Simulation is complete.

In this code:

  • We create instances uA and uB of modules A and B.
  • The TB_INITIAL block in module tb declares and displays the signal.
  • We use hierarchical references to modify and display the signal in other blocks, like TB_INITIAL.signal, and also access signals inside modules A and B.

Upward Name Referencing in Verilog

Verilog also supports upward referencing, allowing lower-level modules to reference items in modules above them in the hierarchy.

Example: Upward Name Referencing

module A;
    task display();
        $display("Hello, this is A");

        // Upward referencing: Access signal in TB_INITIAL
        #5 TB_INITIAL.signal = 1;
    endtask
endmodule

Here, module A references TB_INITIAL.signal defined in the tb module, thus modifying the signal in the higher-level module. This is called upward referencing.

Simulation Log Output:

xcelium> run
Hello, this is A
TB signal = 1
signal_1 = 1
signal_2 = 0
xmsim: *W,RNQUIE: Simulation is complete.

Example with Multiple Nested Modules

You can also use upward referencing with multiple nested modules. When a module (such as D) needs to access functions or tasks in its parent or higher-level modules, it can do so by referencing them directly.

module tb;
  A a();

  function display();
    $display("Hello, this is TB");
  endfunction
endmodule

module A;
  B b();
  function display();
    $display("Hello, this is A");
  endfunction
endmodule

module B;
  C c();
  function display();
    $display("Hello, this is B");
  endfunction
endmodule

module C;
  D d();
  function display();
    $display("Hello, this is C");
  endfunction
endmodule

module D;
  initial begin
    a.display();  // Call display in module A
    b.display();  // Call display in module B
    c.display();  // Call display in module C

    a.b.c.display();  // Upward referencing to call display in C
  end
endmodule

Simulation Log Output:

xcelium> run
Hello, this is A
Hello, this is B
Hello, this is C
Hello, this is C
xmsim: *W,RNQUIE: Simulation is complete.

Understanding the Hierarchical Lookup Process

When Verilog encounters an identifier, like b.display() in module D, it performs a hierarchical search:

  1. First, it checks the current scope (module D) for the identifier.
  2. If it is not found, it looks for the identifier in the parent module (C).
  3. If the identifier is still not found, it continues upwards to higher levels until it reaches the top-level module.

Conclusion

By understanding hierarchical reference scope in Verilog, you can manage large and complex designs more effectively. Using hierarchical paths and upward referencing, you can easily access signals, tasks, and functions from other modules in your design. This approach not only reduces the risk of naming conflicts but also improves the modularity and reusability of your Verilog code.

Scroll to Top