UVM同步通信 - sv event, uvm_event, uvm_barrier详解

文章目录

0. 引言

1. uvm_event

1.1 system verilog 中的event

1.2 waiting for an event

1.3 Persistent trigger: triggered built-in method

1.2 UVM中的uvm_event

1.2.1 Pool classes

1.2.1.1 uvm_pool

1.2.1.2 uvm_object_string_pool

1.2.2 uvm_event 的 使用方法

1.2.2.1 触发uvm_event

1.2.2.1.1 uvm_event_callback

1.2.2.2 等待uvm_event

1.2.2.2.1 wait_trigger/wait_ptrigger

1.2.2.2.2 wait_on

2. uvm_barrier


0. 引言

systemverilog 中介绍了semapahore,event和mailbox,以进行线程间的通信,而它们的实例需要同处一个对象中。而在uvm中,涉及多个组件,组件间的同步线程不再只局限于在同一个对象中。如果组件A定义了一个semapahore,event或mailbox,组件B需要与其通信,则组件B中需要通过hierarchy name的方式来引用组件A中的semapahore,event或mailbox。这种方式与tb间的耦合极为紧密,为将来环境变动,环境复用带来很大的麻烦。

为此,uvm中定义了uvm_event, uvm_barrier,通过全局资源池(唯一的),环境中的任何组件都可以从资源池获取共享的对象句柄,避免组件之间的互相依赖。

uvm定义了如下元件,来实现组件间的同步:

  1. uvm_event, uvm_event_poll, uvm_event_callback
  2. uvm_barrier, uvm_barrier_poll

本文通过源码介绍uvm_event,uvm_barrier 的机制。


1. uvm_event

1.1 system verilog 中的event

sv中一个以event数据类型声明的变量被称为named event,它提供了基础同步对象的句柄。当一个进程等待一个将触发的event,该进程会被同步对象放进它所维护的queue中。进程可以通过@操作符或wait() 来等待一个将要触发的named event,以检查它们的触发状态。

两种触发方式,-> 和 ->>,主要区别是->>的语句会在timeslot中(re)NBA region中调用,而前者是(re)active。

1.2 waiting for an event

@阻塞了调用的进程,直到event被触发。

@需要早于 -> ,否则 进程依然阻塞。

1.3 Persistent trigger: triggered built-in method

事件的triggered内建方法,可以用来获取事件的触发状态,该状态持续一个time step。

hierarchical_event_identifier.triggered

使用这种机制,无论等待操作是在触发操作之前还是在相同的仿真时间执行,事件触发都将解除等待进程的阻塞。因此,triggered方法有助于消除当触发和等待操作同时发生时常见的竞争条件。一个阻塞等待事件(@)的进程可能会解除阻塞,也可能不会,这取决于等待和触发进程的执行顺序。然而,一个等待触发状态(wait)的进程总是会解除阻塞,无论等待和触发操作的执行顺序如何。

举例:

1. 正常操作:使用@ 等待事件。在1ns之后 触发事件。

    event done;

    initial begin 
        fork 
            begin
                $display("%0t, Waitted the event done, before",$time);
                @done;
                $display("%0t, Waitted the event done, after",$time);
            end 

            begin 
                #1;
                -> done;
                $display("%0t, trigger the event done, after",$time);
                
            end
        join
    end

结果:

0, Waitted the event done, before
1, trigger the event done, after
1, Waitted the event done, after

是一对一的,触发一次,最多只能等到一次。即,如果

@done;
                $display("%0t, Waitted the event done, after",$time);
@done;
                $display("%0t, Waitted the event done, after",$time);

触发后,也只显示一个。

2. 先触发事件,1ns后使用@等待事件:

    event done;

    initial begin 
        fork 
            begin
                #1;
                $display("%0t, Waitted the event done, before",$time);
                @done;
                $display("%0t, Waitted the event done, after",$time);
            end 

            begin 
                -> done;
                $display("%0t, trigger the event done, after",$time);
                
            end
        join
    end

结果。没有等到事件的触发。

0, trigger the event done, after
1, Waitted the event done, before

3. 使用@, 同时触发和等待。

            begin 
                #1;
                -> done;
                $display("%0t, trigger the event done, after",$time);
            end
            begin 
                #1;
                $display("%0t, Waitted the event done, before",$time);
                @done;
                $display("%0t, Waitted the event done, after",$time);
            end 

该结果是:

1, trigger the event done, after
1, Waitted the event done, before

调换begin end block之后:

            begin 
                #1;
                $display("%0t, Waitted the event done, before",$time);
                @done;
                $display("%0t, Waitted the event done, after",$time);
            end
            begin 
                #1;
                -> done;
                $display("%0t, trigger the event done, after",$time);
            end

1, Waitted the event done, before
1, trigger the event done, after
1, Waitted the event done, after

对于编译器实则不能区分同一时刻,在同一时刻,还是存在顺序,与编译器有关(在这里,vcs 是将begin...end block顺序执行)。

以下使用wait(done.triggered)

4. 同一仿真时刻,触发和wait

            begin 
                #1;
                -> done;
                $display("%0t, trigger the event done, after",$time);
            end
            begin 
                #1;
                $display("%0t, Waitted the event done, before",$time);
                wait(done.triggered);
                $display("%0t, Waitted the event done, after",$time);
            end

结果:

1, trigger the event done, after
1, Waitted the event done, before
1, Waitted the event done, after

调换之后:

            begin 
                #1;
                $display("%0t, Waitted the event done, before",$time);
                wait(done.triggered);
                $display("%0t, Waitted the event done, after",$time);
            end
            begin 
                #1;
                -> done;
                $display("%0t, trigger the event done, after",$time);
            end

结果:

1, Waitted the event done, before
1, trigger the event done, after
1, Waitted the event done, after

5. 先触发,1ns 之后 wait

            begin 
                #1;
                $display("%0t, Waitted the event done, before",$time);
                wait(done.triggered);
                $display("%0t, Waitted the event done, after",$time);
            end
            begin 
                -> done;
                $display("%0t, trigger the event done, after",$time);
            end

结果:

0, trigger the event done, after
1, Waitted the event done, before

该结果并没有等到事件的触发,这是由于事件触发的状态只维持一个time step。1ns已经开始了一个新的time step。将#1 改为#0,使其在同一个time step即可等到事件的触发。

同一time step,可以wait 多次。

OK,开始正题。

1.2 UVM中的uvm_event

不同组件可以共享同一个uvm_event,这不需要通过跨层次传递uvm_event对象句柄来实现共享,它们是通过uvm_event_pool这一全局资源池来实现的。

uvm_component类中包括一个protected变量uvm_event_pool event_pool。该类的衍生如下。

这个event_pool内部具体是什么呢?

1.2.1 Pool classes

1.2.1.1 uvm_pool

实现了一个基于class的动态关联数组。是一个继承于uvm_object的 参数化类。

class uvm_pool #(type KEY=int, T=uvm_void) extends uvm_object;
...

  typedef uvm_pool #(KEY,T) this_type;

  static protected this_type m_global_pool;
  protected T pool[KEY];
...

  // Function: get_global_pool
  //
  // Returns the singleton global pool for the item type, T. 
  //
  // This allows items to be shared amongst components throughout the
  // verification environment.

  static function this_type get_global_pool ();
    if (m_global_pool==null)
      m_global_pool = new("pool");
    return m_global_pool;
  endfunction

...

  // Function: get_global
  //
  // Returns the specified item instance from the global item pool. 

  static function T get_global (KEY key);
    this_type gpool;
    gpool = get_global_pool(); 
    return gpool.get(key);
  endfunction
...

  // Function: get
  //
  // Returns the item with the given ~key~.
  //
  // If no item exists by that key, a new item is created with that key
  // and returned.

  virtual function T get (KEY key);
    if (!pool.exists(key)) begin
      T default_value;
      pool[key] = default_value;
    end
    return pool[key];
  endfunction
...

m_global_pool 属性定义为static,实现在验证环境中组件间的共享,并在类中维护关联数组 pool[KEY]。

通过get_global 方法获取对象,如果对象不存在就会创建该对象。

1.2.1.2 uvm_object_string_pool

指定uvm_pool的索引类型为"string"。参数化存放的类型。

class uvm_object_string_pool #(type T=uvm_object) extends uvm_pool #(string,T);

  typedef uvm_object_string_pool #(T) this_type;
  static protected this_type m_global_pool;
...
// Function: get_global_pool
  //
  // Returns the singleton global pool for the item type, T. 
  //
  // This allows items to be shared amongst components throughout the
  // verification environment.

  static function this_type get_global_pool ();
    if (m_global_pool==null)
      m_global_pool = new("global_pool");
    return m_global_pool;
  endfunction


  // Function: get_global
  //
  // Returns the specified item instance from the global item pool. 

  static function T get_global (string key);
    this_type gpool;
    gpool = get_global_pool(); 
    return gpool.get(key);
  endfunction
  // Function: get
  //
  // Returns the object item at the given string ~key~.
  //
  // If no item exists by the given ~key~, a new item is created for that key
  // and returned.

  virtual function T get (string key);
    if (!pool.exists(key))
      pool[key] = new (key);
    return pool[key];
  endfunction
...

uvm_event和uvm_barrier,即是对该类,决定存放的类型分别为uvm_event#(uvm_object)和 uvm_barrier。

typedef class uvm_barrier;
typedef class uvm_event;

typedef uvm_object_string_pool #(uvm_barrier) uvm_barrier_pool;
typedef uvm_object_string_pool #(uvm_event#(uvm_object)) uvm_event_pool;

如上,我们知道了uvm_event_pool 里面包含了一个共享的关联数组:

uvm_event#(object) pool [ string ]

通过uvm_event_pool::get_global(xx),以字符串的形式创建或引用一个uvm_event对象。进而在验证环境的组件中使用。

1.2.2 uvm_event 的 使用方法

  • 在组件中使用uvm_event#(xx)来声明该T,xx需要继承于uvm_object。可在build phase中通过get_global在组件中创建或引用该uvm_event,如1.2.1.1所述。
  • 可使用get_num_waiters()来获取等待它的进程数目,可以通过cancel function,在event被disabled或通过其他方式激活触发时,减掉num_waitters,保证计数正确。
1.2.2.1 触发uvm_event

通过event1.trigger(T data=null) ,如下,来触发该event1。与sv event不同的是,它可以选择传递参数,来携带更多的信息。虽然uvm_event参数化类定义了可变的T,但是uvm_event_pool 中定义的数据类型是uvm_event#(uvm_object),即限定了传入的参数类型须是uvm_object或其子类。

此外,uvm_event 也可以通过add_callback(uvm_event_callback cb, bit append=1) 函数来添加回调函数,在cb中定义pre_trigger或post_trigger,在trigger的时候,调用这两个方法。注意在定义pre_trigger 时,return 0才可以trigger event(skip=0)。

virtual function void uvm_event::trigger (T data=null);
		int skip;
		skip=0;
		if (callbacks.size()) begin
			for (int i=0;i<callbacks.size();i++) begin
				uvm_event_callback#(T) tmp=callbacks[i];
				skip = skip + tmp.pre_trigger(this,data);
			end
		end
		if (skip==0) begin
			->m_event;
			if (callbacks.size()) begin
				for (int i=0;i<callbacks.size();i++) begin
					uvm_event_callback#(T) tmp=callbacks[i];
					tmp.post_trigger(this,data);
				end
			end
			num_waiters = 0;
			on = 1;
			trigger_time = $realtime;
			trigger_data = data;
		end
	endfunction
1.2.2.1.1 uvm_event_callback
virtual class uvm_event_callback#(type T=uvm_object) extends uvm_object;
...
  virtual function bit pre_trigger (uvm_event#(T) e, T data);
    return 0;
  endfunction
...
  virtual function void post_trigger (uvm_event#(T) e, T data);
    return;
  endfunction
...
1.2.2.2 等待uvm_event
1.2.2.2.1 wait_trigger/wait_ptrigger

在uvm_event中,使用wait_trigger()/wait_ptigger(), wait_trigger_data(data)/wait_ptrigger_data(data) 来等待将要触发的事件。具体定义如下:

	virtual task uvm_event::wait_trigger_data (output T data);
		wait_trigger();
		data = get_trigger_data();
	endtask
    
	virtual task uvm_event::wait_ptrigger_data (output T data);
		wait_ptrigger();
		data = get_trigger_data();
	endtask
    
    
	virtual task wait_trigger ();
		num_waiters++;
		@m_event;
	endtask

	virtual task wait_ptrigger ();
		if (m_event.triggered)
			return;
		num_waiters++;
		@m_event;
	endtask

ptrigger 是针对event的persistent trigger,如1.3。wait_ptrigger 将触发视为在给定时间片内持久存在,从而避免了某些竞争条件。如果在触发之后但仍在同一时间片内调用此方法,调用者将立即返回。

注意等到事件触发后,并没有清除掉num_waiters。如果后续想获得等待该event的进程数量,需要在等到触发后进行reset。

1.2.2.2.2 wait_on

uvm_event还提供了wait_on task来等待event 的第一次的激活触发。主要基于内部变量on。该变量在trigger时,置1,然后一直保持1,直到event 被reset。

如果调用的时候已经被触发了,则立刻返回。

	virtual task wait_on (bit delta = 0);
		if (on) begin
			if (delta)
				#0;
			return;
		end
		num_waiters++;
		@on;
	endtask


	virtual function void reset (bit wakeup = 0);
		event e;
		if (wakeup)
			->m_event;
		m_event = e;
		num_waiters = 0;
		on = 0;
		trigger_time = 0;
	endfunction

​​​​​​​2. uvm_barrier

uvm_barrier与uvm_event的实现方法类似。使用uvm_barrier_pool来管理uvm_barrier对象,如1.2.1。

不同的是,它可以设置一定的等待阈值(threshold),仅在有不少于该阈值的进程在等待该对象时 才触发该事件,同时激活所有正在等待的进程,使其继续进行。

class uvm_barrier extends uvm_object;

  local  int       threshold;
  local  int       num_waiters;
  local  bit       at_threshold;
  local  bit       auto_reset;
  local  uvm_event#(uvm_object) m_event;
...

  virtual task wait_for();

    if (at_threshold)
      return;

    num_waiters++;

    if (num_waiters >= threshold) begin
      if (!auto_reset)
        at_threshold=1;
      m_trigger();
      return;
    end

    m_event.wait_trigger();

  endtask
...

设置阈值时,如果当前等待事件的对象已经高于了阈值,则会复位,num_waitters清零,并选择性立即触发该事件或复位event。

  virtual function void set_threshold (int threshold);
    this.threshold = threshold;
    if (threshold <= num_waiters)
      reset(1);
  endfunction

  virtual function void reset (bit wakeup=1);
    at_threshold = 0;
    if (num_waiters) begin
      if (wakeup)
        m_event.trigger();
      else
        m_event.reset();
    end
    num_waiters = 0;
  endfunction

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值