always_ff or always_comb for clock generation in a simple TestBench - system-verilog

Which one is to be used with clock generation for a simple testbench?
For eg:
always #20 clk <= ~clk
if I change it to either always_ff or always_comb both gives error
Statements in an always_comb shall not include those that block, have
blocking timing or event controls, or forkjoin statements. The always_ff
procedure imposes the restriction that it contains one and only one event
control and no blocking timing controls.

Neither. Stick to the ordinary always. always_comb and always_ff are for your design.

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.

event control "#" in systemverilog in uvm defined AFTER assignments

I'm trying to understand the UVM driver code defined in a "verificationguide.com" UVM env example : https://www.edaplayground.com/x/5r89
In the mem_driver.sv file, in the drive() task, the following code can be found :
if(req.wr_en) begin // write operation
DRIV_IF.wr_en <= req.wr_en;
DRIV_IF.wdata <= req.wdata;
#(posedge vif.DRIVER.clk);
end
How is the event control #(posedge vif.DRIVER.clk); line written AFTER the events being controlled?
Help me understand the intent of the developer in this case?
It's there to make sure wr_en is held active for at least one clock cycle before the next requested transaction. Otherwise the next transaction would immediately set it back to 0.

System Verilog Clocking block

I am trying to perform a simple test with demo code of Clocking block, but encountered the error.
The code could be find at "EDA playground" http://www.edaplayground.com/x/3Ga
And the error says:
** Error: testbench.sv(38): A default clocking block must be specified to use the ##n timing statement.
** Error: testbench.sv(40): A default clocking block must be specified to use the ##n timing statement.
I think the clocking block has already been specified in the code.
Any Help?
As the error message says, you have to define the clocking block as default:
default clocking cb_counter #(posedge Clock);
Full code here: http://www.edaplayground.com/x/37_
The SV 2012 standard specifies that the ##n operator can only be used if there is a default clocking block defined for the module/program/interface, otherwise it wouldn't be able to know what clock event to use for the delay.
##N delays are not a very useful feature unless you can put them in the same module or interface that the clocking block is defined in. That is typically not the case because you usually put your driver code inside a class inside a package.
repeat (N) #cb_counter;
This works uniformly, even if referencing the cb through a virtual interface.
To resolve the error add default clocking cb_counter; after your clocking block.
SystemVerilog IEEE1800-2012 Section 14 Covers Clocking Blocks.
// Test program
program test_counter;
// SystemVerilog "clocking block"
// Clocking outputs are DUT inputs and vice versa
clocking cb_counter #(posedge Clock);
default input #1step output #4;
output negedge Reset;
output Enable, Load, UpDn, Data;
input Q;
endclocking
default clocking cb_counter; //<-- Set default clocking
// Apply the test stimulus
initial begin
//..
Below I have included a my method of creating a testbench clock, with the initial it is easy to work out when it will be triggered, compared to the original always the time for the first trigger my vary depending on how when the Clk is initialised.
initial begin
Clk = 0;
forever begin
#5ns Clk = ~Clk;
end
end
If you simulator allows system-verilog, I would use #5ns so that it does not rely on the timestep, I find this to more readable and reliable for code reuse.
The version from the question, used an always block.
timeunit 1ns;
// Clock generator
always
begin
#5 Clock = 1;
#5 Clock = 0;
end

Must use non-blocking assignment in a procedural block in System Verilog?

I've met a situation where I think I have to use blocking assignment in a #(posedge clk) block. The snippet below is from a tsetbench. I want to input the data to a DUT through the testbench. input_intf is the name of an interface and wcb is a clocking block synchronizing to wclk.
#(posedge input_intf.wclk)
begin
input_intf.winc = 1;
input_intf.wcb.wdata = 8'd7;
input_intf.winc = 0;
end
I want to do the following: after the positive clock edge, inc=1 and data reading is enabled. After data is read, let inc=0. I guess maybe winc signal has no need to be synchronous with wclk? If it's the case then the snippet should be like following.
input_intf.winc = 1;
#(posedge input_intf.wclk)
input_intf.wcb.wdata <= 8'd7;
input_intf.winc = 0;
So is it true that we must use non-blocking assignment in a procedural block? When learning Verilog, I was told it's true.
Thanks!
The statement input_intf.wcb.wdata = 8'd7; is illegal. You do not use blocking or non-blocking assignments to drive clocking block outputs. See 14.16.1 Drives and non-blocking assignments in the IEEE Std 1800-2012 LRM. For other non-clocking block variables inside your interface, you use the same rules that you learned with Verilog: use non-blocking assignments to write to variables that are synchronous to a clock, and will be read by other procedural blocks on the same clock edge. That is how you avoid races.
The use of clocking blocks is generally an all or nothing approach. you should not be mixing writing to clocking block and non-clocking variables in the same procedure, and you should only be using the clocking block event to synchronize your code.
#(input_intf.wcb) // do not use posedge of any signal here.
input_intf.wcb.wdata <= 8'd7;
There are exceptions to these rules, of course, but I would make sure you know exactly how clocking blocks work before going there.

Why does my sim with a clock never finish?

I added a clock generator to my module, and now the simulation never finishes.
always #10 clk = ~clk;
Why does the sim not finish after all initial code is done?
module test;
reg clk;
initial begin
clk = 0;
$display("Hello");
end
always #10 clk = ~clk;
endmodule
Sim results on EDA Playground: http://www.edaplayground.com/s/4/15
When you're using an always block, you need to have $finish statement in one of your initial blocks to finish the sim.
Fixed code:
module test;
reg clk;
initial begin
clk = 0;
$display("Hello");
$finish(); // <-- FIX
end
always #10 clk = ~clk;
endmodule
Sim results here: http://www.edaplayground.com/s/4/16
That said, if you also have a SystemVerilog program in your environment, the sim will automatically finish after all the initial code in your program blocks is done. From section 24.3 of IEEE 1800-2012 standard:
When all initial procedures within a program have reached their end,
that program shall immediately terminate all descendent threads of
initial procedures within that program. If there is at least one
initial procedure within at least one program block, the entire
simulation shall terminate by means of an implicit call to the $finish
system task immediately after all the threads and all their descendent
threads originating from all initial procedures within all programs
have ended.
There's no fundamental difference between initial and always: always is just initial forever. As long as anything is scheduled for future execution, then the simulation will continue (unless it is explicitly stopped); your statement continuously re-schedules the clock assignment, so the sim never stops. There really is nothing special about initial: it's not even guaranteed to run before any always blocks.
This wasn't an SV question (which I know nothing about), but I was interested to see VL's comment about automatically finishing the sim when the initial code is finished. This would break Verilog compatibility, and I'd be interested to see an LRM reference that confirms this.