Are Program Blocks Necessary?
That’s a frequent SystemVerilog question I’m asked.
Program blocks came directly from donation of the Vera language to SystemVerilog by Synopsys , and try to mimic the scheduling semantics that a PLI application has interacting with a Verilog simulator. So coming from a Vera background, program blocks make perfect sense and do help people transitioning from Vera to SV. But looking at SV from scratch, they are just extra language baggage.
Who would have ever thought we’d be having language wars within the same language! 😮
As far as I can tell, a program block by itself only addresses two race conditions between the testbench and DUT, both of which are covered by using a clocking block by itself.
- Erroneous use of blocking assignments for sequential logic. You have a race within your DUT regardless of the race between your testbench and DUT.
- Erroneous use of non-blocking assignments in combinational gated clock logic. You may have a race within your DUT regardless of the race between your testbench and DUT.
As a user, if you don’t understand why these create races within your DUT, you’re going to have the same races within your testbench, and there’s nothing a program block does that prevent races within your testbench. There lays the false sense of security of having a race-free testbench.
Using a clocking block by itself takes care of the same testbench to DUT races that a program block addresses, plus it takes care of the races caused by non-zero delay skews introduced by gate-level propagation. It does this by the use of the input skews for sampling and output skews for driving.
Now, in addition to the false sense of security, and the redundancy with clocking blocks, here are some additional reasons why I don’t recommend using program blocks
- If you have legacy Verilog testbench code, sometimes you want to share legacy BFM tasks by having your “class” based testbench call those BFM tasks. You’re going to run into nasty timing problems if that task was designed to be scheduled in the active region, and now is scheduled in the re-active. Sampling will be off by a clock cycle. You’ll have even nastier problems if some tasks are called from a program block, and other are still called from a module.
- One person’s Design IP is another person’s Verification IP. At the system level (ESL), there is less of a distinction between models written to represent higher level abstractions of the design, versus part of a testbench. You can’t have differences in scheduling just because one time it’s called from a program, and another time it’s called from a module. Same problem with C code called from a program block or module.
- Unless you’re an experienced Vera user, there is the unexpected surprise that your simulation exits immediately after the thread in your program block ends. Again this is an issue with mixing legacy testbenches, or mixed-language testbenches.
- Most advanced users can barely understand the scheduling semantics of SystemVerilog even without using program blocks. Why introduce unnecessary complexity. Many other enviroments, like SystemC and VHDL have been in production for years without needing the kind of scheduling semantics the program block introduces. Quick quiz: How can you get an assertion pass and fail in the same time slot?
The SystemVerilog language has had many hands involved with its development, including yours and mine. I’m not dismissing anyone efforts, but sometimes you have to take a step back and realize how bloated the language has become. Just because some feature exists in the LRM doesn’t justify that it needs to be used. Let me tell you about virtual interfaces… 👿
Dave Rich
Comments
Leave a Reply
You must be logged in to post a comment.
Waiting to hear about the “Virtual Interfaces” story! I’ve always found the usage of program blocks confusing in SV.
I don’t know If I said it already but …I’m so glad I found this site…Keep up the good work I read a lot of blogs on a daily basis and for the most part, people lack substance but, I just wanted to make a quick comment to say GREAT blog. Thanks, 🙂
A definite great read..Tony Brown
Hi Dave Rich,
Very interesting article. But I’m looking forward
to read the article about Virtual Interfaces 🙂
Bests.
Hi Dave Rich,
Very useful information…Thanks a lot.
Thanks Dave Rich. Your article is very clear and pointed to the topic.
Always had this doubt…thanks a lot !
Hi Dave,
Thank you very much for such a good article. I stuck with your quick quiz :). Can you please, let me know, how to make assertion pass and fail in same time slot.
Hi Dave,
I have a Question.
From my experience, when using virtual interfaces inside classes there are races between the RTL and the testbench. So, unless it is important to run the run_test() command for running UVM test from a program and not a module, I don’t see how you can avoid the usage of clock blocks.
Could you explain how you are avoiding this?
Just to clarify, when I am saying that there are races between the TB and the RTL, I mean that sometimes the I/F signals are being updated before and sometimes after the event of posedge of the clock signal that is part of the I/F.
Lior, If you use non-blocking assignments ‘<=' to RTL signals from your testbench, you will avoid race conditions the same way that RTL code avoids race conditions. See my DVCon paper: http://events.dvcon.org/2012/proceedings/papers/01P_3.pdf
Hi Dave,
Thanks for the response. Using the non-blocking assignament restrict you to write code the way that the design does, which is not the most efficient way to write it (from the simulator resourses point of view).
Please consider the following code using clock blocks (simple AXI monitor):
forever
begin
wait (axi_vif.axi_ar_if.cb.ARVALID & axi_vif.axi_ar_if.cb.ARREADY);
#some monitoring code
@(axi_vif.cb);
end
while using the non-blocking assignments for the I/F you have to write it like this:
forever
begin
@(posedge axi_vif.axi_global_if.ACLK);
if (axi_vif.axi_ar_if.ARVALID & axi_vif.axi_ar_if.ARREADY)
begin
#some monitoring code
end
end
You can see that without using clock blocks, the simulator will need to reevaluate the if condition every posedge of the clock, which can be avoided when using clock blocks. Do you know a way to save simulator resources without those type of clock blocks?
You can write
@(axi_vif.cb iff axi_vif.axi_ar_if.cb.ARVALID & axi_vif.axi_ar_if.cb.ARREADY);
or you can write
@(posedge axi_vif.axi_global_if.ACLK iff (axi_vif.axi_ar_if.ARVALID & axi_vif.axi_ar_if.ARREADY));
You should not be using the wait statement in code that is supposed to by synchronous.
My point is that there is no need for program blocks at all. You can use clocking blocks OR non-blocking assignments to avoid race conditions.
If you have further questions, please us the verificationacademy.com forum where it is easier to format and read code samples.
Dave
Where can I find the blog about virtual interface?
I wrote a program on verification of memory which is as follow:
interface mem_sg (input wire clock);
logic rst;
bit [7:0] address;
logic cs;
logic wr_rd;
bit [7:0] data_in;
logic [7:0] data_out;
modport mem2 (input rst,cs,wr_rd,data_in,output data_out,inout address);
modport dri2 (output rst,cs,wr_rd,data_in,data_out,address);
endinterface
class mem_obj;
bit [7:0] data;
bit [7:0] addr;
bit rd_wr;
bit rst;
endclass
class driver;
virtual mem_sg sg1;
mailbox #(mem_obj) trans1;
mem_obj obj2;
function new(virtual mem_sg sg1, mailbox #(mem_obj) trans1);
begin
this.sg1 = sg1;
sg1.address = 0;
sg1.cs = 0;
sg1.wr_rd = 0;
sg1.data_in = 0;
sg1.rst=0;
this.trans1=trans1;
end
endfunction
task drive_mem ();
begin
integer i = 0;
for (i=0; i Address : %x Data : %x\n”,obj2.addr,obj2.data);
end else begin
sg1.cs=0; $display(“Read -> Address : %x\n”, obj2.addr);
end
@ (posedge sg1.clock);
sg1.cs = 0;
sg1.rst=1;
end
end
endtask
endclass
class generator;
mem_obj obj2;
virtual mem_sg sg1;
integer num_cmds;
mailbox #(mem_obj) trans2;
function new (mailbox #(mem_obj) trans2);
this.trans2 =trans2;
endfunction
task gen_random();
begin
integer i = 0;
for (i=0; i < 15; i ++ )
begin
obj2 = new();
obj2.addr = $urandom_range(0,6);
$display("random addr %x",obj2.addr);
obj2.data = $random();
$display("random data %x",obj2.data);
obj2.rd_wr = $random();
$display("random read %x",obj2.rd_wr);
trans2.put(obj2);
end
end
endtask
endclass
module memory();
reg [7:0] mem [0:6];
reg clk;
mem_sg sg1(
.clock (clk)
);
// top top1 (sg1);
initial begin
clk = 0;
end
always #1 clk = ~clk;
always @ (*)
if (sg1.wr_rd == 1 && sg1.cs == 1) begin
mem[sg1.address] <= sg1.data_in;
end
always @ (*)
if (sg1.wr_rd == 0 && sg1.cs ==0)
sg1.data_out <= mem[sg1.address];
else
sg1.data_out= 'bzz;
always @(sg1.rst)
if(sg1.rst==1 && sg1.cs==0)
begin
mem[0:6] = '{0,0,0,0,0,0,0};
end
endmodule
program top();
mailbox #(mem_obj) trans= new(15);
generator gen = new(trans);
driver dr1 = new(sg1,trans);
initial begin
@(posedge sg1.clock);
$dumpfile("dump.vcd");
$dumpvars;
fork
gen.gen_random();
dr1.drive_mem();
join_any
repeat(30)@(posedge sg1.clock);
end
endprogram
How can i use modport in this progrm??
Dave,
I’m not able to visualize below points. Can you please explain it with an example.
1. Erroneous use of blocking assignments for sequential logic. You have a race within your DUT regardless of the race between your testbench and DUT.
2. Erroneous use of non-blocking assignments in combinational gated clock logic. You may have a race within your DUT regardless of the race between your testbench and DUT.
Thanks
Thank you so much, Dave!
Thank you Dave for this blog.
I found it while searching for the reasons my simulation stops at the end of the program block. Now I got the answer.
This being said, my current (tool) problem is that I can’t see the value of my ‘variables’ defined in the initial block within the top module. I have to define the ‘variable’ with my top signals in the module instead of inside the initial block to see them. This is wrong. I should define variable in the innermost (most local) region, but still see them in the debugger.
Thanks for the informative blog!
jf
You’re welcome.
Sorry, I can’t provide tool specific help on public forums (and even if I could, you didn’t mention which tool you were using). My only suggestion is naming the begin block with a label, begin:name.