Driving a wire from a task in an interface

I am trying to create a simple model of my UVM testbench to test out some functionality I would like to add for wires. My simple interface looks like this:

interface int_1(input logic clk);

  logic [3:0] x;
  logic [13:7] y; // 6 bits
  logic w;
  wire z;

  // Some other tasks to drive x, y, w

  task set_z_one();
    z = 1'b1; 
  endtask

  task set_z_zero();
    z = 1'b0; 
  endtask

  task z_wiggle();
    z = 1'b0;
    @(posedge clk);
    z = 1'b1;
    @(posedge clk);
  endtask


However I am getting this error:

Error-[IBLHS-NT] Illegal behavioral left hand side
basic/int_1.sv, 90
  Net type cannot be used on the left side of this assignment.
  The offending expression is : z
  Source info: z = 1'b1;

But I am driving wires in my UVM drivers just fine with almost the same syntax! Can someone please explain to me what I am doing wrong? I can’t replace the wire type with logic because the point of the simple model is specifically to test a new functionality out for wires.

For comparison, this code is my big UVM testbench works fine: (Changed variable and other names for clarity, please ignore small spelling errors)
IFC:

interface big_ifc (input bit clk);
    wire a;
    wire b;
    wire [5:0] x;

    clocking cbpad_out @ (posedge clk);
        output a;
        output b;
        output x;
    endclocking : cbpad_out

    clocking cbpad_in @ (posedge clk);
        input a;
        input b;
        input x;
    endclocking : cbpad_in

    modport out_if_mp (clocking cbpad_out);
    modport in_if_mp (clocking cbpad_in);

endinterface : big_ifc


UVM Driver

class big_driver extends uvm_driver #(big_item);

    protected virtual big_ifc m_big_vif;

    // Bunch of driver stuff

    task set_all_x_high();
        m_big_vif.x <= 6'b111_111;
    endtask

endclass : big_driver

 

In reply to jms8:

see assigning interface net-type signals from class | Verification Academy

Ben Cohen
http://www.systemverilog.us/ ben@systemverilog.us
For training, consulting, services: contact Home - My cvcblr


  1. SVA Alternative for Complex Assertions
    Verification Horizons - March 2018 Issue | Verification Academy
  2. SVA: Package for dynamic and range delays and repeats | Verification Academy
  3. SVA in a UVM Class-based Environment
    SVA in a UVM Class-based Environment | Verification Horizons | Verification Academy

In reply to jms8:

They’re both illegal.

P1800-2012, section 25.9:

In order for a net to be driven via a virtual interface, the interface itself must provide a procedural means to do so. This can be accomplished either via a clocking block or by including a driver that is updated by a continuous assignment from a variable within the interface.

Your example driver code doesn’t use your clocking block, but are you in fact actually using it? That makes all the difference.

class big_driver extends uvm_driver #(big_item);

    protected virtual big_ifc m_big_vif;

    // Bunch of driver stuff

    task set_all_x_high();
        m_big_vif.x <= 6'b111_111;
    endtask

endclass : big_driver

 

[/quote]

If your simulator is allowing the above example code, looks to me like it violates the standard.

In reply to warnerrs:
Below is a link to a simple model that demonstrates that a wire (data) in an interface can be drivern by a task in a class.

http://SystemVerilog.us/fv/ifc_driver.sv
Key elements from above link


interface dut_mem_if (input logic clk);
        ...
	wire[31:0] data;  // <------------------- the bi-direct
	
	clocking driver_cb @ (posedge clk);
		default input #setup_time output #hold_time;
		output rst_n, rd, wr, address; //  kind_cp; 
		input hold;
		inout data;   // <------------------- the bi-direct 
	endclocking : driver_cb
endinterface : dut_mem_if 

class dut_mem_driver; 
	string tID="DRIVER";
	virtual interface dut_mem_if.drvr_if_mp  vif;
 // ...
	virtual task write_task(logic [31:0] data, address);
		vif.driver_cb.rst_n <= 1'b1;
		...
		this.vif.driver_cb.data <= data;
		this.vif.driver_cb.address <= address; 
		repeat(3) @ (this.vif.driver_cb);
		
		this.vif.driver_cb.wr <= 1'b0;
		this.vif.driver_cb.rd <= 1'b0; 
		this.vif.driver_cb.data   <= 'Z;
	endtask : write_task
endclass : dut_mem_driver 

module top; 
   event e; // for debug
    bit clk, a, b;  
    dut_mem_driver dut_mem_driver1; 
    dut_mem_if  dut_mem_if1(clk); 
    initial forever #10 clk=!clk; 

    initial begin
        dut_mem_driver1 = new(); 
        dut_mem_driver1.vif= dut_mem_if1;  
        #30 -> e;
         @(posedge clk);
        dut_mem_driver1.write_task(5,100);
        -> e; 
        @(posedge clk);
        dut_mem_driver1.write_task(7,200);
        ->e  ; 
        @(posedge clk);
        $stop; 
    end      
endmodule   

 

Ben Cohen
http://www.systemverilog.us/ ben@systemverilog.us
For training, consulting, services: contact http://cvcblr.com/home


  1. SVA Alternative for Complex Assertions
    https://verificationacademy.com/news/verification-horizons-march-2018-issue
  2. SVA: Package for dynamic and range delays and repeats - SystemVerilog - Verification Academy
  3. SVA in a UVM Class-based Environment
    https://verificationacademy.com/verification-horizons/february-2013-volume-9-issue-1/SVA-in-a-UVM-Class-based-Environment

In reply to ben@SystemVerilog.us:

In reply to warnerrs:
Below is a link to a simple model that demonstrates that a wire (data) in an interface can be drivern by a task in a class.

I totally agree. OP’s example driver class is directly driving the wire from a class, not going through a clocking block, which I think is illegal, and is what I was pointing out. Some tools may allow that though.

In reply to warnerrs:

I totally agree. OP’s example driver class is directly driving the wire from a class, not going through a clocking block, which I think is illegal, and is what I was pointing out. Some tools may allow that though.

I believe that driving a wire thru a task is illegal.
Quote:
1800-2012 14.3 Clocking block declaration

  • A clockvar whose clocking_direction is inout shall behave as if it were two clockvars, one input and one output, having the same name and the same clocking_signal.
  • Reading the value of such an inout clockvar shall be equivalent to reading the corresponding input clockvar.
  • Writing to such an inout clockvar shall be equivalent to writing to the corresponding output clockvar.

Here is a simple model:


module top; 
  timeunit 1ns;     timeprecision 100ps;    
    bit clk, a, b; 
    wire w;  
	 
    initial forever #10 clk=!clk; 
    task t(); 
        w = 1'b1;  // ILLEGA !!!!
    endtask
    
    initial begin
        #10; 
        t(); 
        $stop; 
    end
endmodule  

On Edit code - EDA Playground
I gat “w is not a valid left-hand side of a procedural assignment.”
I also get an illegal message on other tools.
Thus, to drive a wire thru a task, you got to go thru a clocking block and modport and variable clocking_direction as inout

Ben SystemVerilog.us

In reply to ben@SystemVerilog.us:

Thanks so much for your answers! I have added clocking blocks and that solves the problem.

For the big_driver/ big_ifc example, the variable x is not driven through a clocking block. However, I omitted the run phase which has a case statement to call the different tasks based on the command in the transaction.

    virtual task run_phase(uvm_phase phase);
        forever begin
            // Grab the next sequence item to process
            `uvm_info(tID, "Waiting for next item to process", UVM_DEBUG)
            seq_item_port.get_next_item(req);
            $cast(rsp, req.clone());
            rsp.set_id_info(req);
            id = rsp.get_transaction_id();

            `uvm_info(tID, $sformatf("Trans (%0d): Starting\n%s", id, rsp.sprint()), UVM_DEBUG)

            // Record the transaction
            void'(begin_tr(rsp));

            // Now call the correct task/function based on the command
            case (req.command)
                big_command::DEFAULT_INPUTS : setDefaultInputs();              // Set the default input values for our pins
                big_command::SET_ALL_X_HIGH : set_all_x_high();           
                default        : `uvm_error(tID, $sformatf("Trans (%0d) : Currently don't know how to implement the request", id))
            endcase

            // Done with this transaction
            `uvm_info(tID, $sformatf("Trans (%0d) : Finished with transaction", id), UVM_DEBUG)
            end_tr(rsp);
            seq_item_port.item_done();
        end
    endtask : run_phase

Does this count as a “procedural means to drive a net through a virtual interface”? The small example and the big testbench have the same simulator, so there must be something in my omitted code that causes the difference.

Regardless, my problem is solved. Thanks for the help!

In reply to jms8:

I was in the SVA 1800 committee, but not in the main sv language committee.
It is odd that you can put a value on a wire thru a procedural block (task) using a clocking block. 1800 says “Writing to such an inout clockvar shall be equivalent to writing to the corresponding output clockvar”. Looks to me that this is bypass way to remove the restriction that a wire cannot be driven by a procedural block.

Would like to hear about this topic from someone in the language committee who can clarify how this came about.
Ben systemverilog.us

In reply to ben@SystemVerilog.us:

It is illegal to make procedural assignments to wires. When you declare clocking block outputs, you are declaring another signal associated with the wire or variable of the same name. You use a clocking block drive statement to schedule an assignment to a clocking block output variable, which then gets assigned to the original signal.

In reply to dave_59:

In reply to ben@SystemVerilog.us:
It is illegal to make procedural assignments to wires. When you declare clocking block outputs, you are declaring another signal associated with the wire or variable of the same name. You use a clocking block drive statement to schedule an assignment to a clocking block output variable, which then gets assigned to the original signal.

Thanks Dave! I see the following equivalence for “data” from what you are describing
:

 clocking driver_cb @ (posedge clk);
		default input #setup_time output #hold_time;
		output rst_n, rd, wr, address; //  kind_cp; 
		input hold;
		inout data;   // <------------------- the bi-direct 
	endclocking : driver_cb
wire[31:0] data;  // <------------------- the bi-direct
Logic[31:0] data_var;  // <------------------- the cb temp

always_comb #hold_time data=data_var:
// nonblocking assignment to data is equivalent to a nonblocking assessment to data_var, and 
// then data_var gets assigned to data after a hold time, as defined in the clocking block. 

Questions: in what version (year) of 1800 did the clocking block came about? Also, was this the result of uvm?
Thanks, Ben systemverilog.us

In reply to ben@SystemVerilog.us:

Questions: in what version (year) of 1800 did the clocking block came about? Also, was this the result of uvm?

Clocking blocks were introduced by the 2002 Vera language donation to SystemVerilog 3.1. In Vera, you do not communicate directly with Verilog signals. It set up a PLI sampling and driving mechanism, and the clocking block was intended to replace that. This was all long before there was an AVM/OVM/UVM.

In reply to dave_59:

Clocking block really slows down the simulation speed.
I always try not to use them.