I am working on a SystemVerilog testbench for a D Flip-Flop (DFF) design, and I’m having trouble with event synchronization in the verification environment. The generator task generates 5 transactions and triggers the gen_done event when finished, but the environment reports that the generator is still running, even after the event is triggered.
task run();
repeat (num_transactions) begin
trans = new();
assert(trans.randomize());
trans.display("Generator");
gen2drv.put(trans);
end
$display("Generator: Finished generating %0d transactions.", num_transactions);
// Debugging
$display("Generator: Waiting to trigger gen_done...");
-> gen_done;
$display("Generator: gen_done event triggered...");
endtask
And in the environment:
task run();
fork
gen.run();
drv.run();
mon.run();
scb.run();
join
if (gen_done.triggered) begin
$display("Generator has finished.");
end else begin
$display("Generator is still running.");
end
endtask
if (drv_done.triggered) begin
$display("Driver has finished.");
end else begin
$display("Driver is still running.");
end
if (mon_done.triggered) begin
$display("Monitor has finished.");
end else begin
$display("Monitor is still running.");
end
if (scb_done.triggered) begin
$display("Scoreboard has finished.");
end else begin
$display("Scoreboard is still running.");
end
// Wait for all events to be triggered
wait (gen_done.triggered && drv_done.triggered && mon_done.triggered && scb_done.triggered);
$display("All tasks completed.");
output:
Checking if all tasks are completed…
Generator is still running.
Driver has finished.
Monitor has finished.
Scoreboard has finished.
You do not show enough code, but I am assuming that your gen.run() method finishes at a time before the others methods. The triggered method is only true in the time slot when the event is triggered–as soon as time advances, it goes back to zero.
But since you are using a fork/joint construct, I’m not sure why you even need the events because the join does not happen until after all the run methods have finished.
I expected the event to remain triggered until checked, but as you mentioned, it seems the event resets after the time slot advances.
I was using the events to explicitly track the completion of each component. Do you suggest removing the events entirely and relying solely on the fork/join behavior for synchronization? Or is there a better way to track task completion without running into this issue?
I’d appreciate any further guidance. Thanks again!
class environment;
generator gen;
driver drv;
monitor mon;
scoreboard scb;
virtual dff_if vif;
mailbox gen2drv;
mailbox mon2scb;
event gen_done; // Signals that the generator has finished generating transactions
event drv_done; // Signals that the driver has finished driving transactions
event mon_done; // Signals that the monitor has finished monitoring transactions
event scb_done; // Signals that the scoreboard has finished comparing transactions
function new(virtual dff_if vif, int num_transactions);
this.vif = vif;
gen2drv = new();
mon2scb = new();
gen = new(gen2drv, gen_done, num_transactions);
drv = new(vif, gen2drv, drv_done, num_transactions);
mon = new(vif, mon2scb, mon_done, num_transactions);
scb = new(mon2scb, scb_done, num_transactions);
endfunction
task run();
fork
gen.run();
drv.run();
mon.run();
scb.run();
join
$display("Checking if all tasks are completed...");
if (gen_done.triggered)
$display("Generator has finished.");
else
$display("Generator is still running.");
if (drv_done.triggered)
$display("Driver has finished.");
else
$display("Driver is still running.");
if (mon_done.triggered)
$display("Monitor has finished.");
else
$display("Monitor is still running.");
if (scb_done.triggered)
$display("Scoreboard has finished.");
else
$display("Scoreboard is still running.");
// Wait for all events to be triggered
wait (gen_done.triggered && drv_done.triggered && mon_done.triggered && scb_done.triggered);
$display("All tasks completed.");
// Generate report
scb.report();
$display("Functional Coverage: %0.2f%%", mon.trans.cg.get_coverage());
// Terminate the simulation
$display("Simulation finished.");
$finish;
endtask
endclass
class generator;
rand dff_transaction trans;
mailbox gen2drv;
event gen_done; // Event to signal when the generator is done
int num_transactions; // Number of transactions to generate
function new(mailbox gen2drv, event gen_done, int num_transactions);
this.gen2drv = gen2drv;
this.gen_done = gen_done;
this.num_transactions = num_transactions;
endfunction
task run();
$display("Generator: Starting to generate %0d transactions.", num_transactions);
repeat (num_transactions) begin
trans = new();
assert(trans.randomize());
trans.display("Generator");
gen2drv.put(trans);
end
$display("Generator: Finished generating %0d transactions.", num_transactions);
-> gen_done;
endtask
endclass
If all of your run methods have a definitive termination, then the simple fork join construct works. But in most testbenches the driver and monitor processes run indefinitely. So it’s only when some of the processes finish that you want to end the test. You can do that using a semaphore as described here.
The UVM has objection and barrier classes that help you out when to know the test is done.