Clock generator task - forever statement makes the simulator hang

Hi everyone, verification newbie here. I am trying to write a task that generates two clock signals, below is my code. I tested it on EDA Playground and it works fine, but when I move it into my actual project it makes the simulator hang / the testcase never complete. What is the correct way to do this?

    //50 MHz clock signal generation
    task clock_gen();
        fork
            clk1 = 0;
            clk2 = 0;        
            forever #10 clk1 <= ~clk1;
            forever #10 clk1 <= ~clk2;
        join   
    endtask

In reply to ianmurph:
There is no reason to declare the clock signals of type logic; declaring them as type bit is sufficient. On
forever #10 clk1 <= ~clk1;
forever #10 clk1 <= ~clk2;
Did you mean forever #10 clk2 <= ~clk2; ??


module top; 
  bit clk1, clk2; 
  logic clk3=0, clk4=0;  // initialized to 0 in the declaration
  initial forever #10 clk1 = !clk1;
  initial forever #10 clk2 = !clk2; // Questioning why 2 clocks of same frequency???
 // If you have clock division use the always_ff
 always_ff @(posedge clk1) clk3  <= !clk3; 

In reply to ianmurph:
If you did declare these clock with 4-state logic/reg, you have a race condition that could be eliminated this way:

task clock_gen();
   clk1 = 0;
   clk2 = 0;        
        fork
           
            forever #10 clk1 <= ~clk1;
            forever #10 clk1 <= ~clk2;
        join   
endtask

If that does not fix your hang, you will need to provide more details on how you think your testbench is hanging.

In reply to ben@SystemVerilog.us:

Yes that was a copy paste typo when I was making this post. I changed the names of the clks to make it easier to read. I am going to post my actual testbench code below. I trust your solution would work but I am trying to put it inside of a task. Also when I write “initial forever” inside my task the tool throws an error. I am going to post all my code below.

Could you please share a place where you call this clock_gen task?
I see that inside it uses fork ... join which blocks execution until all nested threads are completed and since forever never ends by definition this fork ... join will never release execution. So I suspect that the hang might be caused by this.

In reply to dave_59:

Hi Dave, thanks for the reply. That did not fix my issue unfortunately, so I am going to post the testbench code below. I am going to try and delete out the irrelevant stuff to make it easier to read.

TB Module:

timescale 1ns/1ns

import header_pkg::*;
import common_pkg::*;
import testcase_pattern_generator_pkg::*;

module tb_fpga();

	// Pattern Generator interface
    pattgen_if          pattgen_if();

    // Group: Class Constructs
    //////////////////////////////////////////////////////

    // Contains misc tools to add wait states and monitor interrupt on a signal
    common_pkg::events #(`CLOCK_PARAM) ev;    // task_wait_x_seconds, task_wait_clock_cycles

    // Class used to validate the test results.
    common_pkg::scoreboard scrb;

    // Testcases
	testcase_pattern_generator_pkg::class_testcase_pattern_generator #(`CLOCK_PARAM) testcase_pattern_gen;

// instantiate the pattern_gen_v1_0 for the pattern generator test
pattern_gen_v1_0 #(
    .C_AXIS_TDATA_WIDTH(24),
	.C_S_AXI_DATA_WIDTH(32),
	.C_S_AXI_ADDR_WIDTH(32)
)	
	pattern_gen_inst
(
	.i_video_vsync(pattgen_if.video_vsync),
	.o_vsync_int(pattgen_if.vsync_int),
	.i_axis_aclk(pattgen_if.axis_aclk),
	.i_axis_aresetn(pattgen_if.axis_aresetn),
	.s_axis_tdata(pattgen_if.s_axis_tdata),
	.s_axis_tvalid(pattgen_if.s_axis_tvalid),
	.s_axis_tready(pattgen_if.s_axis_tready),
	.s_axis_tuser_sof(pattgen_if.s_axis_tuser_sof),
	.s_axis_tlast(pattgen_if.s_axis_tlast),
	.m_axis_tdata(pattgen_if.m_axis_tdata),
	.m_axis_tvalid(pattgen_if.m_axis_tvalid),
	.m_axis_tready(pattgen_if.m_axis_tready),
	.m_axis_tuser_sof(pattgen_if.m_axis_tuser_sof),
	.m_axis_tlast(pattgen_if.m_axis_tlast),
	.s_axi_aclk(pattgen_if.s_axi_aclk),
	.s_axi_aresetn(pattgen_if.s_axi_aresetn),
	.s_axi_awaddr(pattgen_if.s_axi_awaddr),
	.s_axi_awprot(pattgen_if.s_axi_awprot),
	.s_axi_awvalid(pattgen_if.s_axi_awvalid),
	.s_axi_awready(pattgen_if.s_axi_awready),
	.s_axi_wdata(pattgen_if.s_axi_wdata),
	.s_axi_wstrb(pattgen_if.s_axi_wstrb),
	.s_axi_wvalid(pattgen_if.s_axi_wvalid),
	.s_axi_wready(pattgen_if.s_axi_wready),
	.s_axi_bresp(pattgen_if.s_axi_bresp),
	.s_axi_bvalid(pattgen_if.s_axi_bvalid),
	.s_axi_bready(pattgen_if.s_axi_bready),
	.s_axi_araddr(pattgen_if.s_axi_araddr),
	.s_axi_arprot(pattgen_if.s_axi_arprot),
	.s_axi_arvalid(pattgen_if.s_axi_arvalid),
	.s_axi_arready(pattgen_if.s_axi_arready),
	.s_axi_rdata(pattgen_if.s_axi_rdata),
	.s_axi_rresp(pattgen_if.s_axi_rresp),
	.s_axi_rvalid(pattgen_if.s_axi_rvalid),
	.s_axi_rready(pattgen_if.s_axi_rready)
); 
	
endmodule

Interface:

/////////////////////////////////////////////////////////////
// Pattern Generator Interface
/////////////////////////////////////////////////////////////
interface pattgen_if;	

	// all the pattern generator IOs are connectd to the interface:
	logic video_vsync;
	logic vsync_int;
	bit axis_aclk;
	logic axis_aresetn;
	logic [23:0] s_axis_tdata;
	logic s_axis_tvalid;
	logic s_axis_tready;
	logic s_axis_tuser_sof;
	logic s_axis_tlast;
	logic [23:0] m_axis_tdata;
	logic m_axis_tvalid;
	logic m_axis_tready;
	logic m_axis_tuser_sof;
	logic m_axis_tlast;
	bit s_axi_aclk;
	logic s_axi_aresetn;
	logic [31:0] s_axi_awaddr;
	logic [2:0] s_axi_awprot;
	logic s_axi_awvalid;
	logic s_axi_awready;
	logic [31:0] s_axi_wdata;
	logic [3:0] s_axi_wstrb;
	logic s_axi_wvalid;
	logic s_axi_wready;
	logic [1:0] s_axi_bresp;
	logic s_axi_bvalid;
	logic s_axi_bready;
	logic [31:0] s_axi_araddr;
	logic [2:0] s_axi_arprot;
	logic s_axi_arvalid;
	logic s_axi_arready;
	logic [31:0] s_axi_rdata;
	logic [1:0] s_axi_rresp;
	logic s_axi_rvalid;
	logic s_axi_rready;
	
endinterface

Testcase Class:

// CONTENTS
// ===========================
// 43  : ____
// 44  : Functions

// 143 : TestCases
// ---------------------------

package testcase_pattern_generator_pkg;

// Import common tools (scoreboard, events, ...)
import common_pkg::*;

class class_testcase_pattern_generator #(MONITOR_CLOCKS=0, NUM_CLOCKS=1) extends common_pkg::test_base #(.NUM_OF_TESTS(1));
	
		// Verification class used to monitor testcases. <scoreboard>
    common_pkg::scoreboard  scrb;

    // Used tp monitor interrupts and clocks. <events>
    // System Clock
    common_pkg::events #( .MONITOR_CLOCKS(MONITOR_CLOCKS), .NUM_CLOCKS(NUM_CLOCKS)) ev;
	
	virtual ps_if vps_if;
	//module interfaces
    virtual pattgen_if vpattgen_if;
   
    // INDEX:   .new(clock_if, ps_if)
    function new(
        virtual clock_if #(MONITOR_CLOCKS, NUM_CLOCKS) vclock_if,
        virtual ps_if vps_if,
        virtual pattgen_if vpattgen_if
    );
        super.new("Pattern Generator");
        scrb = new();
        ev   = new(vclock_if, 0);
        this.vps_if = vps_if;
        this.vpattgen_if = vpattgen_if;
    endfunction
	
    // INDEX:   .task_call_testcase(num)
    task task_call_testcase(input int num, output error);
        case(num)
            'd0:    input_stimulus();
        endcase
    endtask
    
	//----------------------------------------------------------------------------------------------------
    // INDEX: ____
    // INDEX: Functions
    //----------------------------------------------------------------------------------------------------
    
    //reset generation
    task reset_gen();
        $display("[%0t]: Reseting TPG", $time);
		vpattgen_if.axis_aresetn = 0;
        vpattgen_if.s_axi_aresetn = 0;
        #340;  //toggle the reset lines (active low) - need to be held for 16 clock cycles  = 340ns               
        vpattgen_if.axis_aresetn = 1;
        vpattgen_if.s_axi_aresetn = 1;
    endtask
    
    //50 MHz clock signal generation
    task clock_gen();
		fork
			vpattgen_if.axis_aclk = 0;
			vpattgen_if.s_axi_aclk = 0;
            forever #10 vpattgen_if.axis_aclk <= ~vpattgen_if.axis_aclk;
            forever #10 vpattgen_if.s_axi_aclk <= ~vpattgen_if.s_axi_aclk;
			
        join   
    endtask
    
    task signal_init();
        //Video signals     
        vpattgen_if.video_vsync = 0;             // vsync from slave video stream
        vpattgen_if.s_axis_tvalid = 0;           // input video valid signal
        vpattgen_if.s_axis_tuser_sof = 0;        // input video start of frame
        vpattgen_if.s_axis_tlast = 0;            // input video end of line
        vpattgen_if.m_axis_tready = 1;           // output ready signal
        
        //AXI signals
        vpattgen_if.s_axi_awprot = "000";        // defines the access permissions for write accesses (3 bit)
        vpattgen_if.s_axi_arprot = "000";        // defines the access permissions for read accesses(3 bit)
        vpattgen_if.s_axi_wstrb = 4'b1111;       // write strobes - not used(4 bit)
        vpattgen_if.s_axi_arvalid = 0;           // read address valid
        vpattgen_if.s_axi_rready = 0;            // read address ready
        vpattgen_if.s_axi_bready = 1;            // response ready - indicates that the master can accept a write response
        vpattgen_if.s_axi_awvalid = 1;           // write address valid
        vpattgen_if.s_axi_wvalid = 1;            // write data valid
    endtask
    
    task write_reg(input int wsize, input int addr);//, output int error);
        wait (vpattgen_if.s_axi_wready == 1'b1); //wait for wready signal
        vpattgen_if.s_axi_awaddr = addr;
        vpattgen_if.s_axi_wdata = wsize;
        wait (vpattgen_if.s_axi_bvalid == 1'b1); //wait for write successful confirmation
        //TO DO: display error message here if o_s_axi_bvali does not go high, indicating failed axi write 
        //#80;
    endtask
    
task input_stimulus();
      begin
		
		$display("Test has started");
		fork
			reset_gen();
            clock_gen(); 	//forever statements causing simulation to hang
            signal_init();
        join 
		
		#2000;
		//$finish;
      end
   endtask
	
endclass
endpackage

In reply to Andrei Pazniak:

Hi Andrei, that makes sense. I tried added the fork join because only one of the two clocks get generated without it. When I added the fork-join both clocks were being generated (in EDA Playground) Is there a reason only one clock would be generated without the fork-join?

In reply to ianmurph:

A forever statement is an infinite procedural loop. It will never execute the next procedural statement that follows it unless you break out of the loop. If you put the forever statement as part of a fork/join, the the statement after the join will never execute the statement that follows it (in this case, the implicit return of the task).

You probably want to use fork/join_none instead. It does not wait for any of its statements to finish before executing the next statement.

In reply to ianmurph:

Completely agree with Dave, fork … join_none is likely what you want. In case if you need more control over spawned threads I recommend reading chapter 9 “Processes” of IEEE Std 1800-2017.

Thank you Dave and Andrei, the fork/join_none looks like it resolved my issue