SystemVerilog_线程
SV线程
语句块
用来将多个语句组织在一起,使得他们在语法上如同一个语句。
- 顺序块:语句置于关键字begin和end之间,块中的语句以顺序方式执行。
- 并行块:关键字fork和join/join_any/join_none之间的是并行块语句,块中的语句并行执行。
命名块
给每个块定义的标识名称,将块名加在begin或fork后面;
- 可以定义块内局部变量
- 允许定义的块被其他语句调用,如disable语句;
程序和模块
- 对于硬件的过程块,他们之间的通信可以理解为不同逻辑、时序块之间的通信或者同步,是通过信号的变化来完成
- 从硬件实现角度看,verilog 通过always,initial过程语句块和信号数据连接实现进程间通信
- 可以将不同的module作为独立的程序块,他们之间的同步通过信号的变化(event触发)、等待待定事件(时钟周期)或者时间延时来完成
- 从软件思维理解硬件仿真,仿真中的各个模块首先是独立运行的线程
- 模块在仿真一开始便并行执行,除了每个线程会按照自身内部产生的事件来触发过程语句块之外,也同时依靠相邻模块间的信号变化来完成模块之间的线程同步
线程
- 线程是可以独立运行的程序
- 线程需要被触发,可以结束或者不结束
- 在module中的 initial 和 always ,都可以看成独立的线程,他们在仿真 0 时刻开始,而选择结束或者不结束
- 硬件模型中由于都是always 语句块,所以可以看成是多个独立运行的线程,而这些线程会一直占用仿真资源
- 软件测试平台中的验证环境都需要有initial 语句块去创建,而在仿真过程中,验证环境中的对象可以创建和销毁,因此,软件测试端的资源占用是动态的
- 线程的执行轨迹呈树状结构,任何线程都有父线程
- 当子线程终止时,父线程可以继续执行
- 当父线程终止时,其所开辟的所有子线程都应当会终止
- 软件环境中的initial 块对语句有两种方式:
- begin ... end:内部语句以顺序方式执行
- fork ... join:内部语句以并发方式执行
线程的控制
创建线程
fork...join/join_any/join_none: 能够从它的每一个并行语句中产生并发进程。
- join: 父进程阻塞到这个分支产生的所有进程结束;
- join_any: 父进程阻塞到这个分支产生的任意一个进程结束;
- join_none: 父进程会与其生成的所有子进程并发执行。在父进程执行一条阻塞语句或者结束之前,所生成的子进程不会启动执行。
wait fork
- 在SV中,当程序中的initial 块全部执行完毕,仿真器就退出了。
- 如果需要等待fork 中的所有线程执行完毕再退出结束initial 块,可以使用 wait fork 语句来等待所有子线程结束
initial begin: fork_join_none $display("@%0t, fork_join_none_thread entered", $time); fork: fork_join_none_thread thread(0, 10); // 线程1 thread(1, 20); // 线程2 thread(2, 30); // 线程3 join_none $display("@%0t, fork_join_none_thread exited", $time); wait fork; // 等待所有线程退出 $display("@%0t, fork_join_none_thread's all sub-threads finished", $time); end
disable
- 在使用fork ... join 或者fork ... join_none 以后,可以使用disable 来制定需要停止的线程
- disable fork 可以停止从当前线程中衍生出来的所有子线程
- 如果给某一个任务或者线程指明标号,那么当这个线程被调用多次以后,如果通过disable 去禁止这个线程标号,所有衍生的同名线程都将被禁止
initial begin: fork_join_any $display("@%0t, fork_join_any_thread entered", $time); fork: fork_join_any_thread thread(0, 10); thread(1, 20); thread(2, 30); join_any $display("@%0t, fork_join_any_thread exited", $time); disable fork_join_any_thread; $display("@%0t, disabled fork_join_any_thread", $time); end
线程间的通信:IPC
- 测试平台中的所有贤臣都需要同步并交换数据
- 一个线程等待另一个线程
- 多个线程可能同时访问同一个资源
- 线程之间可能需要交换数据
event
- Verilog 中,一个线程总是要等待一个带 @ 操作符的事件。这个操作符是边沿敏感的,总是阻塞着、等待事件的变化
- 其他线程可以通过 -> 操作法来触发事件,结束对第一个线程的阻塞
- event 是边沿触发
- 可以使用电平敏感的 wait(e1.triggered()) , 来代替边沿敏感的阻塞语句 @e1;只要event 被触发过,就可以防止引起阻塞
- 明确什么时候使用wait:wait 用于等待一次,如果需要多次等待,则需要在等待到一次后,就清除一次,如uvm中的wait;@ 可以等待多次
event e1, e2; initial begin ->e2; @e1; // 触发e1,在event 边沿触发 end initial begin ->e1; // 等待e1 @e2; end // e1, e2在同一时刻被触发,由于delta cycle的时间差,可能使得两个初始化无法等到e1 或者 e2 // 推荐使用 wait (e1.triggered());
线程间的通知
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 driver(); fork this.launch(); this.move(); join endtask endclass module road; initial begin automatic car byd = new(); byd.driver(); end endmodule
envet 模式
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 driver(); fork this.launch(); this.move(); join endtask endclass
semaphore
- 可以实现对同一资源的访问控制,互斥访问
- semaphore 的三种基本操作:
- new() 可以创建一个带单个或者多个钥匙的semaphore
- get() 可以获取一个或者多个钥匙
- put() 可以返回一个或者多个钥匙
- try_get() : 非阻塞式获取钥匙,函数返回 1 表示有足够多的钥匙,返回 0 表示钥匙不够
mailbox信箱
- 线程间如果传递消息,可以使用mailbox
- mailbox和队列有相近之处
- mailbox 是一种对象,需要使用 new() 例化;例化时有一个可选的参数 size 来限定其存储的最大数量
- 如果size 是0 或者没有指定 new(),则信箱是无限大的,可以容纳任意多的条目
- new(N), 设置容量为 N
- put():可以把数据放入mailbox;如果信箱已满,则 put 会阻塞;try_put() 非阻塞方法
- get():可以从信箱移除数据;如果信箱为空,则 get 会阻塞;try_get() 非阻塞方法
- peek():可以获取对信箱里数据的拷贝而不移除它;try_peek 非阻塞方法
- 线程之间的同步方法需要注意,哪些是阻塞方法,哪些是非阻塞方法
- 如果要显式的限定mailbox 中的元素类型,可以通过mailbox #(type=T) 的方式来声明,例如 mailbox #(int)
mailbox | 队列 |
必须通过new()例化 | 只需要声明队列 |
可以同时存储不同的数据类型 | 内部存储的数据类型必须一致 |
put 和 get 是阻塞方法 调用阻塞方法只能在task中,因为阻塞是耗时的 |
push_back 和pop_front是非阻塞的 在使用queue 时,需要使用wait(queue.size()>0)才可以在其后对非空队列做取数据操作 |
mailbox只能用作FIFO | queue 既可以用作FIFO, 也可以用作LIFO |
mailbox变量的操作,在传递形式参数是,实际传递并拷贝的是mailbox指针 |
传递队列的形式参数默认是input方向,传递的是数组的拷贝; 考虑使用ref方向,考虑对队列是引用还是拷贝 |
总结
- event:最小信息量的触发,即单一的通知功能。可以用来做事件的触发,也可以多个event组合起来做线程之间的同步
- semaphore:共享资源的安全卫士。如果多线程之间要对某一公共资源做访问
- mailbox:精小的SV原生FIFO。在线程之间做数据通信或者内部数据缓存是可以考虑这个