线程以及线程间的通讯
- 程序和模块
模块,将不同的module作为独立的程序块,他们之间的通讯联系如下:信号的变化,或者等待事件的触发、等待相应的时间从硬件实现角度,通过过程语句块(always,initial)和信号数据的连接
线程(独立运行的程序),每个线程通过自己内部产生的事件来触发过程块语句,并且通过相邻模块之间的信号变化来完成线程间的同步硬件模块都是always语句块,可以看成是多个独立运行的内存,不会结束,占用内存;软件测试中采用initial,可以创建或者销毁,是动态存在的
- 进程与变量
子进程可以在其内部定义本地变量,也可以访问fork以外的变量,默认条件下,父进程和子进程都可以修改父进程定义的变量。
fork join_none与静态变量搭配时,所有的线程只能访问一个同名的静态变量(类中静态变量声明以后无论例化多少个对象,只可以共享一个同名的静态变量),在循环过程中通过fork join_none访问访问同名的静态变量实质上是访问最后一个循环下的静态变量,这是由于共享变量的通讯间的竞争引起的,可以将静态变量改为automatic使fork join_none在循环过程中访问独立的变量而非共享静态变量。
- 线程的使用
fork...join/fork...join_none/fork...join_any
- 等待线程
wait fork
disable:停止从当前线程中衍生出来的所有子线程(关闭的线程是都指代指代与不指代,不指代的情况下范围是往外扩展的);停止被多次调用的任务:比如在任务中在某种条件下触发了disable A,而在随后的initial语句块中调用了三次A,仅仅其中一次A中的条件触发了disable A,那么disable会把所有的A全部关掉,而不是仅仅触发该条件的A(线程同名导致)
- 线程间的通讯
event(一个线程等待另一个):
主要是->(发起事件)与@,wait之间的区别,如果在同一时间内(->和@,wait在同一时间),先等一个信号,再发送信号是可以等待到,而先发送信号,后等待的化仅仅wait可以等待到,而@等待不到;但是如果是不在同一时间内,对于wait和@而言,比如在10ns时开始等待信号,但是信号在10ns之前已经发送了,那么wait和@均等待不到,如果是在10ns开始发送信号,而wait和@在此之前就在等了,那么两者都能等到(汽车加速的例子)。
semaphore(多个线程同时访问同一个数据):
一些基本操作:new(),创建,不写代表创建0个;get(),获取,get()是阻塞的,如果想获取一个semaphore而不被阻塞,可以采用try_get()函数,返回1表示有足够的钥匙而返回0表示钥匙不够;put(),返回(对于get和put默认都是归还1把钥匙)
get/put操作中需要注意的地方:(1)对于semaphore来说,如果只有一把钥匙,get以后一定要put,如果没有put,下次的get会一直等待钥匙归还,此时程序会死锁(2)semaphore中可以直接还钥匙,semaphore对谁来拿钥匙,拿几把,谁来还钥匙,还几把,均没有要求,所以使用semphore会经常出现错误,但是程序不会报错,因此在使用过程中一定要遵循谁拿谁还的原则。
semaphore的几个应用:
应用1:产生两个总线任务的线程,然后只创建一个钥匙,由于存在两个线程,在获取总线钥匙的时候,实际上只会有一个线程拿到钥匙,处理完后由取钥匙的那个对象放回(一定要放回否则会死锁,并且注意一定要拿钥匙的那个线程返还钥匙,semaphore允许另一个对象直接返还,并且程序不会报错,所以一定要遵循谁拿谁还)
program automatic test(bus_ifc.TB bus);
semaphore sem;
initial begin
sem = new(1);
fork
sequencer();
sequencer();
join
end
task sequecer;
repeat($urandom%10) @bus.cb;
senTrans();
endtask
task senTrans;
sem.get(1);
@bus.cb;
bus.cb.addr <= t.addr;
sem.put(1);
endtask
endprogram
应用2 :在创建一把钥匙后,创立两个任务,分别代表拿钥匙和返还钥匙。随后通过fork join中创立两个线程,采用begin end包裹,在begin end中使用针对同一个对象使用前面定义的两个任务,实现谁拿谁还
class car;
semaphore key;
function new();
key = new(1);
endfunction
task get_on(p);
$display("%s is waiting for the key",p);
key.get();
#1ns;
$display("%s got on the car",p);
endtask
task get_off(p);
$display("%s is got off the car",p);
key.put();
#1ns;
$display("%s returned the key",p);
endtask
endclass
module family;
car byte;
string p1 = "wife";
string p2 = "husband";
initial begin
fork
begin
byte.get_on(p1);
byte.get_off(p1);
end;
begin
byte.get_on(p2);
byte.get_off(p2);
end;
join
end
endmodule
应用3:使用车管家实现semaphore的控制:首先定义一个队列表示等待的人,在定义一个字符串表示使用的人。此外车管家主要包含三个部分,第一部分是分发钥匙,要保证队列和钥匙不为0,在从队列中取出一个给字符串(pop_front),并且钥匙归零,第一部分一直进行(forever),用fork join_none避免错误;第二部分是拿钥匙,将给的名字放入队列中(push_back),采用wait使得给的名字为等待的队列中时,拿到钥匙;最后一部分是还钥匙,如果名字和使用的人相同(字符串),清空字符串,钥匙加一。
mailbox(线程之间传递数据,可以充当缓存的作用)
一些基本操作:(1)maibox是一种对象,需要new()例化,默认信箱是无穷大的(括号中不给值默认无穷大),括号内加数字限定长度;(2)阻塞:put() 放入,get() 移除,peek() 拷贝但不移除,如果信箱是空的get会阻塞,如果信箱是满的,put会阻塞; 非阻塞:try_get(),try_put(),try_peek();(3)maibox #(int)限定类型为int;
maixbox与队列之间的关系:(1)mailbox必须通过new()例化,而队列只需要声明(2)mailbox可以存放不同类型的变量,只是不建议这么做,而队列中变量类型必须一致(3)对于mailbox来说get(),put()是阻塞的,而对于队列来说,pop_front和push_back是非阻塞的(4)对列取数前要通过wait(queue.size())>0保证其非空(5)信箱只可以用作FIFO,而队列不仅可以用作FIFO,还可以用作LILO(5)队列要加ref,还有问题?
同步问题:
//event,不用new函数例化event
class car;
event e_stall;
event e_park;
task stall;
#1ns;
-> e_stall;
@e_park;
endtask
task park;
@e_stall;
#1ns;
-> e_park
endtask
task driver();
fork
this.stall();
this.park();
join_none
endtask
endclass
//semphare,用new函数例化信箱
class car;
semphare key;
function new();
key = new(0);
endfunction
task stall;
#1ns;
key.put();
key.get();
endtask
task park;
key.get();
#1ns;
key.put();
endtask
task driver();
fork
this.stall();
this.driver();
join_none
endtask
endclass
//mailbox,可以传输一些参数,定义两个参数实现数据的交换,要用new函数例化信箱
class car;
mailbox mb;
function new();
mb = new(1);
endfunction
task stall;
int val=1;
#1ns;
mb.put(val);
mb.get(val);
endtask
task park;
int val=1;
mb.get(val);
#1ns;
mb.put(val);
endtask
task driver;
fork
this.stall();
this.park();
join_none
endtask
endclass