路科验证的个人空间 https://blog.eetop.cn/1561828 [收藏] [复制] [分享] [RSS]

空间首页 动态 记录 日志 相册 主题 分享 留言板 个人资料

日志

SV组件实现篇之五:组件间的通信

已有 3352 次阅读| 2017-5-22 23:41 |个人分类:验证系统思想|系统分类:芯片设计

文章结构:
  • 线程间通信的需求
  • event(事件)
  • semaphore(旗语)
  • mailbox(信箱)
  • 三要素的异同,以及同其它通信方式的比较
  • 实际的应用场景
  • 组件之间通信的方式,monitor到checker的方式



如果将硬件模块理解为一个个运行的程序的话,那么它们内部的各个过程语句块(always块)即是多个一开始在0时刻就运行的线程(thread),而这些线程也是并发执行,并且不会退出的。对于硬件的过程块,它们之间的通信,即可理解为不同逻辑/时序块之间的通信或者同步,实际是通过信号的变化来完成的。

那么对于软件而言,如果同时又多个线程,例如多个task同时执行,那么又该如何对这些软件上的多个线程进行通信和同步呢?如果我们将多个对象,也抽象为多个线程,那么对于多个对象之间,如果它们之间要通信,又该如何实现呢?

这一节中,我们除了会列出软件常见的几种方式,也会将SV关于线程通信的“三宝”展示给读者。在介绍完后,会将朴素的通信方式和SV原生的方式进行对比,看看SV自带的通信“三宝”有什么优点。最后,我们也将对组件间通信的实际场景给出一些利用上述进程通信的解决办法。而打开本节最佳的方式,我们将会一直围绕着进程间通信的需求来开展,看一看线程之间究竟有什么悄悄话要讲。只有了解了这些根本的需求,我们在日后具体的问题面前,也就开始将其分类,看看究竟是哪一种需求类型,再将其对号入座,用适当的方式去解决。

通知的需求
不同的线程之间,有时会有互相告知的需求。譬如,我们要开一辆车,在踩油门要行使之前,我们首先得看看汽车有没有发动好,那么这辆车子是这样设计的:

class car;
bit start = 0;

task launch();
start = 1;
$display("car is launched");
endtask

task move();
wait(start == 1);
$display("car is moving");
endtask

task drive();
fork
this.launch();
this.move();
join
endtask
endclass

module road;
initial begin
automatic car byd = new();
byd.drive();
end
endmodule

输出结果:
# car is launched
# car is moving

从这个例子可以看到,尽管在car::drive()中同时启动了两个线程car::launch()和car::move(),然而,car::move()通过了两个进程之间共同看到的信号car::start来判断什么时候可以行驶,即利用了wait语句完成从线程launch通知线程move的需求。

如果我们将上面的公共变量修改为event(事件),那么通过事件的触发和判断事件是否被触发过,也可以实现一样的需求:

class car;
event e_start;

task launch();
-> e_start;
$display("car is launched");
endtask

task move();
wait(e_start.triggered);
$display("car is moving");
endtask

task drive();
fork
this.launch();
this.move();
join
endtask
endclass

注意上面在car::move中,使用了wait(start.triggered)来判断车是否已经发动,因为有的时候使用“@”操作符时可能已经错过了汽车发动的事件,但是可以通过start.triggered来判断event是否已经被触发过。

那么,从上面的例子来看,利用公共变量也可以解决线程互相通知的需求,是吗?只有一部分可以用公共变量来解决。例如下面这个例子,当汽车要加速的时候,仪表盘的计数又是如何显示的呢?


class car;
event e_start;
event e_speedup;
int speed = 0;

...

task speedup();
#10ns;
-> e_speedup;
endtask

task display();
forever begin
@e_speedup;
speed++;
$display("speed is %0d", speed);
end
endtask

task drive();
fork
this.launch();
this.move();
this.display();
join_none
endtask
endclass

module road;
initial begin
automatic car byd = new();
byd.drive();
byd.speedup();
byd.speedup();
byd.speedup();
end
endmodule

输出结果:
# car is launched
# car is moving
# speed is 1
# speed is 2
# speed is 3

从上面这个汽车加速的例子来看,如果在公路上你要一直踩着油门不放的话,这个加速的event必定是会被不断触发的,而当线程A要给线程B传递超过一次的事件时,利用公共变量就不再是个好的选择了。上面依然可以通过event的触发,来多次通知另外一个线程。

注意,对于线程多次通知的需求,应该使用“@”,而无法使用wait(event.triggered)。这是由于,当一个event被触发之后,它的状态会使得event.triggered一致保持为true(1'b1)。同时,event也没有一个方法可以清除event.triggered的状态,对于这一点,应该是event应用属性上面的一个遗憾。而我们在后面关于UVM的介绍部分,也会对比uvm_event这个类,相比于SV event的新属性。

资源共享的需求
线程之间除了“发球”和“接球”这样的打乒乓以外,还有更深入的友谊,比如共用一些资源。对于线程间共享资源的使用方式,也应该遵循互斥访问(mutex access)方式。对于共享资源的控制原因在于,如果不对其访问做控制时,可能出现多个线程对同一资源的访问,进而出现不可预期的数据损坏和线程的异常,这种现象称之为“线程不安全”。

还是以这辆BYD为主角,如果丈夫和妻子都要开这辆车,而这辆车只有一把钥匙的话,只能有一定的顺序先后使用,才可以解决开车的问题吧。在SV中,通过semaphore(旗语)来解决这个问题,我们来看看下面开车的例子:


class car;
semaphore key;

function new();
key = new(1);
endfunction

task get_on(string p);
$display("%s is waiting for the key", p);
key.get();
#1ns;
$display("%s got on the car", p);
endtask

task get_off(string p);
$display("%s got off the car", p);
key.put();
#1ns;
$display("%s returned the key", p);
endtask
endclass

module family;
car byd = new();
string p1 = "husband";
string p2 = "wife";
initial begin
fork
begin
byd.get_on(p1);
byd.get_off(p1);
end
begin
byd.get_on(p2);
byd.get_off(p2);
end
join
end
endmodule

输出结果:
# husband is waiting for the key
# wife is waiting for the key
# husband got on the car
# husband got off the car
# husband returned the key
# wife got on the car
# wife got off the car
# wife returned the key

一开始我们在拿到这辆车的时候,只有一把钥匙,而丈夫和妻子如果都想开车的话,也得遵循先到先得的原则。所以,当丈夫和妻子同时都想用车的时候,一把钥匙(semaphore key)只可以交给他们中的一位,另外一位则需要等待,直到那把钥匙归还之后才可以使用。而从上面的输出结果来看,也是能够看出来,虽然丈夫和妻子在同一时间想开这辆车,而只允许一位家庭成员来驾驶这辆BYD。直到丈夫从车上下来,归还了钥匙以后,妻子才可以上车。

我们用这个生动的例子来解释semaphore对于控制共享资源的帮助,从上面对于semaphore key的使用来看,当key在使用前必须要做初始化,即要告诉用户它原生自带几把钥匙,从上面例子来看,它只有1把钥匙。而在丈夫和妻子在等待和归还钥匙时,没有在semaphore的函数中传递参数,实际默认他们等待和归还的钥匙数量也是1。semaphore也可以被初始化为多个钥匙,同时也可以支持每次支取和归还多把钥匙用来控制资源访问,而这种更复杂的场景不在这本书的讨论范畴之内。

如果上面的semaphore,用自定义的类来实现,该如何做呢?

class carkeep;
int key = 1;
string q[$];
string user;

task keep_car();
fork
forever begin
wait(q.size() != 0 && key != 0);
user = q.pop_front();
key--;
end
join_none;
endtask

task get_key(string p);
q.push_back(p);
wait(user == p);
endtask

task put_key(string p);
if(user == p) begin
user = "none";
key++;
end
endtask
endclass

class car;
carkeep keep;

function new();
keep = new();
endfunction

task drive();
keep.keep_car();
endtask

task get_on(string p);
$display("%s is waiting for the key", p);
keep.get_key(p);
#1ns;
$display("%s got on the car", p);
endtask

task get_off(string p);
$display("%s got off the car", p);
keep.put_key(p);
#1ns;
$display("%s returned the key", p);
endtask
endclass

module family;
car byd = new();
string p1 = "husband";
string p2 = "wife";
initial begin
byd.drive();
fork
begin
byd.get_on(p1);
byd.get_off(p1);
end
begin
byd.get_on(p2);
byd.get_off(p2);
end
join
end
endmodule

输出结果:
# husband is waiting for the key
# wife is waiting for the key
# husband got on the car
# husband got off the car
# husband returned the key
# wife got on the car
# wife got off the car
# wife returned the key

上面这个例子中,这对夫妻除了有一辆车之外,还新添了一个智能车管家carkeep。这个管家的作用也使得夫妻不再因为车到底该给谁开而惹得叫谁不开心,它会公平地保管、分发和收回钥匙。不过,目前这个车管家能力有限,只能保管一把钥匙,不过对于一辆车的实际用途来看,已经足够了吧。而且,从实际执行结果来看,也倒不赖。也许今后的某一天,这位妻子会要求carkeep开发人员,把自己的优先级提高那么一点点,使得跟丈夫同时等车钥匙的时候,有多一点的胜算。所以,如果这位妻子想要更“贴心”一点的车管家,恐怕semaphore还不够,自己做一个更智能的carkeep是好的选择吧。


数据通信的需求
对于一辆车子来讲,如果要实时显示这辆车子的状态,会需要多个仪表,而显示的参数也包括了车速、油量、发动机的转速和温度等,而这些涉及到了各个传感器到汽车控制中枢的通信。如果我们继续上面这辆BYD来模拟这些不同传感器(线程)到车子中央显示的通信的话,就可以利用SV的mailbox(信箱)来满足线程之间的数据通信。

class car;
mailbox tmp_mb;
mailbox spd_mb;
mailbox fuel_mb;
int sample_period;

function new();
sample_period = 10;
tmp_mb = new();
spd_mb = new();
fuel_mb = new();
endfunction

task sensor_tmp;
int tmp;
forever begin
std::randomize(tmp) with {tmp >= 80 && tmp <= 100;};
tmp_mb.put(tmp);
#sample_period;
end
endtask

task sensor_spd;
int spd;
forever begin
std::randomize(spd) with {spd>= 50 && spd <= 60;};
spd_mb.put(spd);
#sample_period;
end
endtask

task sensor_fuel;
int fuel;
forever begin
std::randomize(fuel) with {fuel>= 30 && fuel <= 35;};
fuel_mb.put(fuel);
#sample_period;
end
endtask

task drive();
fork
sensor_tmp();
sensor_spd();
sensor_fuel();
display(tmp_mb, "temperature");
display(spd_mb, "speed");
display(fuel_mb, "feul");
join_none
endtask

task display(mailbox mb, string name="mb");
int val;
forever begin
mb.get(val);
$display("car::%s is %0d", name, val);
end
endtask
endclass

module road;
car byd = new();
initial begin
byd.drive();
end
endmodule

输出结果:
# car::temperature is 100
# car::speed is 50
# car::feul is 30
# car::temperature is 96
# car::speed is 55

点赞

评论 (0 个评论)

facelist

您需要登录后才可以评论 登录 | 注册

  • 关注TA
  • 加好友
  • 联系TA
  • 0

    周排名
  • 0

    月排名
  • 0

    总排名
  • 0

    关注
  • 253

    粉丝
  • 25

    好友
  • 33

    获赞
  • 45

    评论
  • 访问数
关闭

站长推荐 上一条 /2 下一条

小黑屋| 关于我们| 联系我们| 在线咨询| 隐私声明| EETOP 创芯网
( 京ICP备:10050787号 京公网安备:11010502037710 )

GMT+8, 2024-4-19 21:55 , Processed in 0.020646 second(s), 12 queries , Gzip On, Redis On.

eetop公众号 创芯大讲堂 创芯人才网
返回顶部