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 thetb
module, which results in an error. - However, the name
signal
can be reused inside the taskdisplay
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
anduB
of modulesA
andB
. - The
TB_INITIAL
block in moduletb
declares and displays thesignal
. - We use hierarchical references to modify and display the
signal
in other blocks, likeTB_INITIAL.signal
, and also access signals inside modulesA
andB
.
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:
- First, it checks the current scope (module
D
) for the identifier. - If it is not found, it looks for the identifier in the parent module (
C
). - 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.