线程的概念
fork join_any 某个子线程执行完退出后,剩下的子线程还会执行
fork join_none不会等待执行子线程,(点火后)直接执行后面线程外语句
- fork
- $display(); //执行这条后就会跳出,执行外面的display
- #10 $display(); //10个单位后会执行
- #20 $display();
- join_any
- $display();
在sV中,当程序中的initial块全部执行完毕,仿真器就退出了。如果我们希望等待fork块中的所有线程执行完毕再退出结束initial块,我们可以使用wait fork语句来等待所有子线程结束。
- fork
- check_trans(tr1);
- check_trans(tr2);
- ……
- join_none
- ……
- wait fork;
使用fork……join_any或者fork……join_none以后,可以使用disable来指定需要停止的线程。
- fork:time_block
- begin
- wait(bus.cb.addr==tr.addr);
- $display("@$0t:Addr match %d",$time,tr.addr);
- end
- #TIME_out $display();
- join_any
- disable timeout_block;
disable fork可以停止从当前线程中衍生出来的所有子线程
- initial begin
- check_trans(tr0); //线程0
- //创建一个线程来限制disable fork的作用范围
- fork //线程1
- begin
- check_trans(tr1); //线程2
- fork //线程3
- check_trans(tr2); //线程4
- join
- //停止线程1-4,单独保留线程0
- #(TIME_OUT/2)disable fork; //停止所在的线程
- end
- join
- end
如果给任务或者线程指明标号,那么线程被调用多次以后,如果通过disable去禁止这个线程标号,所有衍生的同名线程都将被禁止。
- task wait_for_time_out(int id);
- if(id==0)
- fork
- begin
- #2;
- disable wait_for_time_out;
- end
- join_none
- fork:just_a_little
- begin
- ……
- end
- join_none
- endtask
-
- //任务wait_for_time_out被调用了三次,从而衍生出三个线程
- //线程0在#2延时之后禁止了该任务,而由于三个线程均是同名线程,因此这些线程都被禁止
- initial begin
- wait_for_time_out(0);
- wait_for_time_out(1);
- wait_for_time_out(2); //0时刻三个都会执行完,都为fork_join_none。任务退出,线程还在
- #(TIME_OUT)
- end
三个线程里唯一一个不用new的,可以将其当作一个对象,按类处理。
- event e1,e2;
- initial begin
- $display("@%0t:1:before trigger",$time);
- ->e1;
- @e2; //改为wait(e2.triggerd())
- $display("@%0t:1:after trigger",$time);
- end
- initial begin
- $display("@%0t:2:before trigger",$time);
- ->e2;
- @e1;
- $display("@%0t:2:after trigger",$time);
- end
-
- //第一个初始块启动,触发e1事件,然后阻塞在e2上。第二个初始化启动触发e2事件,然后阻塞在e1上。
- 总会有一个after trigger没有执行
- //e1和e2在同一时刻被触发,由于delta cycle的时间差使两个初始化模块无法等到e1或e2
- 更安全的方式是使用event的方法triggered(),那么全部都会打印出来
- //通过wait进行线程通知
- class car;
- bit start=0; //event e_start;
- task launch();
- start=1; //->e_start
- $display();
- endtask
- task move();
- wait(start==1); //wait(e_start.triggerd) 可以实现同样的需求
- endtask;
- task driver();
- fork
- this.launch(); //虽然是并行语句,但由于wait会导致luanch先运行
- this.move();
- join
- endtask
- endclass
- //使用@的情况,不能使用wait triggered,因为边沿触发可以一直触发,而电平触发只能触发一次
- //uvm中的triggered不同,是可以擦除状态的
- class car;
- event speed;
- int speed=0;
- task speedup();
- #10ns;
- ->e_speedup;
- endtask
- task display();
- forever begin
- @e_speedup; //一直在等待
- speed++
- $display("speed is %0d";speed);
- end
- endtask
- module road;
- initial begin
- automatic car byd=new();
- byd.speedup(); //1
- byd.speedup(); //2
- byd.speedup(); //3
- end
- endmodule
- program automatic test(bus_ifc.TB bus);
- semaphore sem; //创建一个semphore
- initial begin
- sem=new(1); //分配一个钥匙
- fork
- sequencer(); //产生两个线程 这里用semphore是为了让只有一个能够进行,new参数为1
- sequencer();
- join
- end
- task sequencer;
- repeat($urandom%10) //随机等待0-9个周期
- @bus.cb;
- sendTrans(); //执行总线事务
- endtask
- task sendTrans;
- sem.get(1); //获取总线钥匙
- @bus.cb;
- bus.cb.addr<=t.addr; //信号驱动到总线
- ……
- sem.put(1); //处理完成后把钥匙返回,要不然会死锁
- endtask
- endprogram
资源共享的需求
- //与前面事件的例子相比,前面是顺序,通过event设置了哪个先哪个后。而在这不知道顺序,
- //虽然两者的结果都是顺序执行
- class car;
- semphore 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 is waiting for the key",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
- //通过类实现同样功能
- class carkeep;
- int key=1;
- string q[$];
- string user;
- task keep_user();
- fork
- forever begin; //管理分发钥匙
- wait(q.size()!=0 && key!=0);
- user=p.pop_front();
- key--;
- end
- join_none;
- endtask
- task get_key(string p);
- q.push(p);
- wait(user==p);
- endtask
- task put_key(string p);
- if(user==p)begin
- key++;
- user="none";
- end
- endtask
- endclass
- class car;
- carkeep keep;
- function new();
- keep=new();
- endfunction
- task driver();
- keep.keep_car();
- endtask
- task get_on(string p);
- $display("%s is waiting for the key",p);
- keep.get_key();
- #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
- program automatic bounded;
- mailbox mbx;
- initial begin
- mbx=new(1); //容量为1
- fork
- for(int i=1;i<4;i++)begin
- $display("Producer:before put(%0d)",i);
- mbx.put(i);
- $display("Producer:after pur(%0d)",i);
- end
- repeat(4)begin
- int j;
- #1ns mbx.get(j);
- $display("Consumer:after get(%0d)",j);
- end
- join
- end
- endprogram
-
- //结果
- Producer:before put(1)
- Producer:after put(1)
- Producer:before put(2)
- Consumer: after get(1)
- Producer:after put(2)
- Producer:before put(3)
- Consumer: after get(2)
- Producer:after put(3)
- Consumer: after get(3)
如果我们继续通过上面这辆BYD,来模拟不同传感器(线程)到车的中央显示的通信,可以利用SV的mailbox (信箱)来满足多个线程之间的数据通信。
- //分别用maibox和队列实现
- class car;
- mailbox tmp_mb,spd_mb; //int tmp_q[$],spd_q[$];
- int sample_period;
- function new();
- sample_period=10;
- tmp_mb=new();
- spd_mb=new();
- endfunction
- task sensor_tmp;
- int tmp;
- forever begin
- std::randomize(tmp) with {tmp>=80 && tmp<=100;}
- tmp_mb.put(tmp); //类里任务调用任务 //tmb_q.push_back(tmp)
- #sample_period;
- end
- endtask
- task sensor_spd;
- int spd;
- forever begin
- std::randomize(spd) with {spd>=50 && spd<=60};
- spd_mb.put(spd); //spd_q.push_back(spd);
- #sample_period;
- end
- endtask
- task driver();
- fork
- sensor_tmp();
- sensor_spd();
- display(tmp_tb,"temperature");
- display(spb_tb,"speed");
- join_none
- endtask
- task display(mailbox mb,string name="mb"); //(string name,ref int q[$])
- int val;
- forever begin
- mb.get(val); //wait(q.size()>0);
- $display("car::%s is %0d",name,val); //val=q.pop_front(); 使用队列记得加上wait
- end
- endtask
- endclass
- module road;
- car byd=new();
- initial begin
- byd.driver();
- end
- endmodule
maibox与queue在使用时的差别:
mailbox的其它特性
将stall和park两个线程的同步视作,先由stall发起同步请求,再等待park线程完成并响应同步请求,最后由stall线程继续其余的程序,最终结束熄火的过程。用之前掌握的SV三种进程通信的方式event、semaphore和mailbox来解决进程间的同步问题。
- //所谓的同步其实是握手
- class car;
- event e_stall;
- event e_park;
- task stall;
- #1ns;
- -> e_stall; //2
- @e_park; //3
- task park;
- @e_stall; //1
- #1ns;
- ->e_park //4
- endtask
- task driver();
- this.stall();
- this.park();
- endclass
- //semphore和maibox get\put顺序相同 两者都是为空时要先放数据
- class car;
- semphore key; //mailbox mb;
- function new();
- key=new(0); //mb=new(1);
- endfunction
-
- task stall; //int val=0;
- #1ns;
- key.put(); //1 //mb.put(val);
- key.get(); //4 //mb.get(val);
- endtask
- task park; //int val=0;
- key.get(); //2 //mb.get(val);
- #1ns;
- key.put(); //3 //mb.put(val);
- endtask
- task driver();
- fork
- this.stall();
- this.park();
- join_none
- endtask
- endclass
正在进行的进程由于发生某事件而暂时无法继续执行时,便放弃处理机而处于暂停状态,亦即进程的执行受到阻塞,我们把这种暂停状态叫阻塞进程阻塞,有时也成为等待状态或封锁状态。通常这种处于阻塞状态的进程也排成一个队列。有的系统则根据阻塞原因的不同而处于阻塞状态进程排成多个队列。
上面都是线程A请求同步线程B,线程B再响应线程A的同步方式
如果要在同步(事件)的同时,完成一些数据传输,那么更合适的是mailbox,因为它可以用来存储一些数据; 而event和semaphore更偏向于小信息量的同步,即不包含更多的数据信息。
通信要素的比较和应用
event:最小信息量的触发,即单一的通知功能。可以用来做事件的触发,也可以多个event组合起来用来做线程之间的同步。
semaphore:共享资源的安全卫士。如果多线程间要对某一公共资源做访问,即可以使用这个要素。
mailbox:精小的SV原生FIFO。在线程之间做数据通信或者内部数据缓存时可以考虑使用此元素。