How can I create a task which drives an output across time without using globals? - system-verilog

I want to write some tasks in a package, and then import that package in some files that use those tasks.
One of these tasks toggles a reset signal. The task is reset_board and the code is as follows:
package tb_pkg;
task reset_board(output logic rst);
rst <= 1'b0;
#1;
rst <= 1'b1;
#1;
rst <= 1'b0;
#1;
endtask
endpackage
However, if I understand this correctly, outputs are only assigned at the end of execution, so in this case, the rst signal will just get set to 0 at the end of the task's execution, which is obviously not what I want.
If this task were declared locally in the module in which it is used, I could refer to the rst signal directly (since it is declared in the module). However, this would not allow me to put the task in a separate package. I could put the task in a file and then `include it in the module, but I'm trying to avoid the nasty complications that come with the way SystemVerilog handles includes (long-story-short, it doesn't work the way C does).
So, is there any way that the task can drive an output with different values across the duration of its execution without it having to refer to a global variable?

A quick solution is to use a ref that passes the task argument by reference instead of an output argument that is copied after returning from the task.
task reset_board(ref logic rst);
There are a few drawbacks of doing it this way. You can only pass variables of matching types by reference, so when you call reset_board(*signal*), signal cannot be a wire. Another problem is you cannot use an NBA <= to assign a variable passed by reference, you must use a blocking assignment =. This is because you are allowed to pass automatic variables by reference to a task, but automatic variable are not allowed to be assigned by NBAs. There is no way for the task to check the storage type of the argument passed to it.
Standard methodologies like the UVM recommend using virtual interfaces or abstract classes to create these kinds of connections from the testbench to the DUT. See my DVCon paper for more information.

Related

System verilog simulation performance for uvm_hdl_read vs assign statement

I need to generate around 10,000 connectivity assertions to check that values driven at DUT interface at the beginning of simulation has reached (and is retained) at around 10,000 points inside the DUT throughout the simulation.
The 10k destination points inside DUT are available through xls, hence, if I can take the paths to an array, I could write a single assertion property and instantiate it multiple times while iterating through the array.
What is the better approach here for simulation performance?
Using assign :
logic[7:0] dest_vals[10000];
assign dest_vals[0] = dut_path0.sig0; //This assignment code can be script-generated
assign dest_vals[1] = dut_path1.sig1;
....
assign dest_vals[9999] = dut_path9999.sig9999;
property check_val(expected_val, dut_val);
#(posedge assert_clk) expected_val == dut_val;
endproperty
genvar i;
generate
for(i=0;i<9999;i++)
assert property check_val(exp_val[i], dest_vals[i]);
endgenerate
Using uvm_hdl_read :
string dest_val_path[10000] = '{
"dut_path0.sig0",
"dut_path1.sig1",
....
"dut_path9999.sig9999"
};
property check_val(expected_val, dut_val);
#(posedge assert_clk) expected_val == dut_val;
endproperty
genvar i;
generate
for(i=0;i<9999;i++) begin
initial forever begin
#(posedge assert_clk);
uvm_hdl_read(dest_val_path[i],dest_vals[i]);
end
assert property check_val(exp_val[i], dest_vals[i]);
end
endgenerate
Follow up question: Is there a way to change static array of size 10k to dynamic array/associative array/queue, so that if 10k destination points changes to 11k in future, array need not be re-sized manually?
Using uvm_hdl_read to access a signal by a string name is significantly slower than a direct hierarchical reference to a signal in an assign statement. A lot of code gets executed to lookup up that signal name in a string database and retrieve it from the simulator's internal database using the SystemVerilog's C based "VPI" routines. Plus, you can lose some optimizations because the value of the signal needs to be readily available at any point in time. In the case of the continuous assign statement, the simulator can work backwards and figure out it only needs the signal value on the assert_clk trigger.
How noticeable this performance difference is depends on a number of other factors
The overall size of the entire design's activity relative to additional code executed by uvm_hdl_read
How often the assert_clk triggers relative to the other activity in the design
How good a job you were able to limit VPI access to just the signals involved. Every signal you give VPI access to increases the size of the string database and prevents optimizations around that signal.
There is another option that does not involve assign statements or generate loops. You can use the bind construct to insert a module with your assertion. This works particularly well in your situation since you already have a script that produces the code for you.
module #(type T=logic [7:0]) mcheck(input logic clk, T expected_val, dut_val);
property check_val;
#(posedge clk) expected_val == dut_val;
endproperty
assert property(check_val);
endmodule
bind dut_path0 mcheck m(tb_path.assert_clk,tb_path.expected_val[0], sig0);
bind dut_path1 mcheck m(tb_path.assert_clk,tb_path.expected_val[1], sig1);
...
Note the port connections in the bind module are relative to the dut_path where mcheck is bound into.

Can someone explain the control flow of modules in System Verilog

I know how to link modules but could someone explain the flow of calling the modules to be used when I want it to be used.
Like have a state machine and depending on the state I can call a module to activate, or like if I need to repeat a process how to go back to a module earlier in a state machine.
again I get the instantiating part like this
wire clk;
wire sig;
wire out;
A a(clk, sig, topout);
B b(clk, sig);
endmodule
but can someone explain how to call modules and how the control flow works in general for them?
(I am new to HDLs so I appreciate any help)
Verilog is a language specifically developed to simulate behavior of hardware. Hardware is a set of transistors and other elements which always statically presented and function in parallel. Functioning of such elements could be enabled or disabled, but the hardware is still present.
Verilog is similar to the hardware in the sense that all its elements are always present, intended for parallel functioning.
The basic functional elements of Verilog are gates, primitives and procedural blocks (i.e., always blocks). Those blocks are connected by wires.
All those elements are then grouped in modules. Modules are used to create logical partitioning of the hardware mode. They cannot be 'called'. They can be instantiated in a hierarchical manner to describe a logical structure of the model. They cannot be instantiated conditionally since they represent pieces of hardware. Different module instances are connected via wires to express hierarchical connectivity between lower-level elements.
There is one exception however, the contents of an always block is pure software. It describes an algorithmic behavior of it and as such, software flow constructs are allowed inside always block (specific considerations must be used to make it synthesizable).
As it comes to simulation, Verilog implements an event-driven simulation mode which is intended to mimic parallel execution of hardware. In other words, a Verilog low level primitive (gate or always block) is executed only if at least one of its inputs changes.
The only flow control which is usually used in such models is a sequence of input events and clocks. The latter are used to synchronize results of multiple parallel operations and to organize pipes or other sequential functions.
As I mentioned before, hardware elements can be enabled/disabled by different methods, so the only further control you can use by implementing such methods in your hardware description. For example, all hardware inside a particular module can be turned off by disabling clock signal which the module uses. There could be specific enable/disable signals on wires or in registers, and so on.
Now to your question: your code defines hierarchical instantiation of a couple of modules.
module top(out);
output wire out;
wire clk;
wire sig;
A a(clk, sig, out);
B b(clk, sig);
endmodule
Module 'top' (missing in your example) contains instances of two other modules, A and B. A and B are module definitions. They are instantiated as corresponding instances 'a' and 'b'. The instances are connected by signals 'clk', which is probably a clock signal, some signal 'sig' which is probably an output of one of the modules and input in another. 'out' is output of module top, which is probably connected to another module or an element in a higher level of hierarchy, not shown here.
The flow control in some sense is defined by the input/output relation between modules 'A' and 'B'. For example:
module A(input clk, input sig, output out);
assign out = sig;
...
endmodule
module B(input clk, output sig);
always#(posedge clk) sig <= some-new-value;
...
endmodule
However, in general it is defined by the input/output relation of the internal elements inside module (always blocks in the above example). input/output at the module port level is mostly used for semantic checking.
In the event-driven simulation it does not matter hardware of which module is executed first. However as soon as the value of the 'sig' changes in always#(posedge clk) of module 'B', simulation will cause hardware in module 'A' (the assign statement to be evaluated (or re-evaluated). This is the only way you can express a sequence in the flow at this level. Same as in hardware.
If you are like me you are looking at Verilog with the background of a software programmer. Confident in the idea that a program executes linearly. You think of ordered execution. Line 1 before line 2...
Verilog at its heart wants to execute all the lines simultaneously. All the time.
This is a very parallel way to program and until you get it, you will struggle to think the right way about it. It is not how normal software works. (I recall it took me weeks to get my head around it.)
You can prefix blocks of simultaneous execution with conditions, which are saying execute the lines in this block when the condition is true. All the time the condition is true. One class of such conditions is the rising edge of a clock: always #(posedge clk). Using this leads to a block of code that execute once every time the clk ticks (up).
Modules are not like subroutines. They are more like C macros - they effectively inline blocks of code where you place them. These blocks of code execute all the time any conditions that apply to them are true. Typically you conditionalize the internals of a module on the state of the module arguments (or internal register state). It is the connectivity of the modules through the shared arguments that ensures the logic of a system works together.

How to use System-Verilog Assertions on modules within top level DUT

I'm trying to write a memory scheduling testbench and to verify that I am accessing the correct addresses and writing the correct values at the right time I want to compare what is going on with the signals inside my top module with my schedule that I developed.
I've tried looking up "dot notation" and using my simulator (ModelSim) to access the signal (which I can do on the waveform fine) but I want to be able to use SVAs to check I have the correct values.
module top(input d, output q)
//wires
wire sub1_output
// Instantiate submodule
sub_module1 sub1(.sub1_output(sub1_output));
always begin
// logic
end
endmodule
module tb_top();
reg d;
wire q;
DUT top(.*);
initial begin
// This is where I want to access something within my DUT
assert(DUT.sub1_output == 1'b1)
end
endmodule
When I try this my code compiles and runs, but if I write the assertion such that it should fail and stop my code, nothing happens.
DUT.sub1_output
Is the correct format to use assertions on signals within top level instantiations.

How to change verbosity of uvm components after certain condition

I am trying to change the UVM verbosity of the simulation after satisfying certain conditions. Verbosity options of different components are passing through the command line as +uvm_set_verbosity. Once the conditions are satisfied, then the simulations should run with the the command line +uvm_set_verbosity option. Till then simulation runs with low verbosity for all components.
Looking through the UVM library code, it appears that there is a function called m_set_cl_msg_args(). This function calls three other functions that appear to consume the command line arguments like: +uvm_set_verbosity, +uvm_set_action, +uvm_set_severity.
So what I did was get the uvm_root instance from the uvm_coreservice singleton, and then use the get_children() function from uvm_component class to recursively get a queue of all of the uvm_components in the simulation. Then call the m_set_cl_msg_args() function on all of the components.
My code looks like:
begin
uvm_root r;
uvm_coreservice_t cs_t;
uvm_component array_uvm[$];
cs_t = uvm_coreservice_t::get();
r = cs_t.get_root();
r.get_children(array_uvm);
foreach(array_uvm[i])
array_uvm[i].m_set_cl_msg_args();
end
Even though this code compiles properly, But this is not changing verbosity. Any idea ?
Moreover I am able to print all the components in array_uvm. So I am guessing
array_uvm[i].m_set_cl_msg_args();
this as a wrong call.
Anyone have any other suggestion to change verbosity during run time.
You should never use functions in the UVM that are not documented in the language reference manual. They can (and do) change in any revision. I'm guessing +uvm_set_verbosity only works at time 0 by default.
There is already a function to do what you want
umm_top.set_report_verbosity_level_hier()
I suggest using +UVM_VERBOSITY=UVM_LOW to start your test, and then define your own switch for activating the conditional setting.
If you want a specific component, use component_h.set_report_verbosity_level() (add the _hier to set all its children)
You can use the UVM's command line processor get_arg_values() method to specify the name of the component(s) you want to set, and then use umm_top.find() to get a handle to the component.

System verilog:: Static Variable non-blocking Assignent outside program-block?

I'm new to system verilog and i'm stuck with a basic concept, kindly provide rationale behind the following behavior:
In System verilog, Why Static class properties declared in other than program-block scope cannot be assigned with blocking assignment from program block?
2.Why is that, even if static variable is assigned with non-blocking statement, the change in that static variable is no visible ($display) immediately, it is available after a delay of say #1.
Example:
class A ;
static int i;
endclass
program main ;
A obj;
initial
begin
obj.i = 123; // Not Allowed, can only be done using <= ... WHY ??
$display(obj.i);
#1 $display(obj.i);
end
endprogram
There is no such rule in the IEEE 1800-2012 LRM Earlier version of SystemVerilog had more restrictions on the types of assignments allowed, but those have all been removed. I do not recommend that anyone use program blocks anymore. There are a big source of unnecessary confusion. See http://go.mentor.com/programblocks
The purpose of "program" block in SystemVerilog is to guarantee that there will not be any race condition between the testbench and the DUT if the user encloses his testbench in program block(s) and keeps the DUT outside of program block(s). Another way to avoid race conditions is implemented by limiting DUT/testbench interaction to interfaces/clocking blocks. Also note that:
a) blocking assignments (since they are executed immediately and therefor the result of the execution can vary with the order of execution of threads) can lead to race conditions
b) hardware (RTL) variables can only be static
Given the whole scenario, the compiler makes out that the blocking statement could lead to a race condition between the DUT and the testbench. And hence the error.
When you use non-blocking assignment, the assignment is scheduled and not executed immediately. It would get executed once the scheduler gets a chance to execute it. And that would happen only after the present thread yields because of a blocking expression that involves time increment. In the given code snippet that happens once the executing thread encounters #1; the $display after #1 sees the result of the non-blocking assignment while the one before does not.