引言
本文简单介绍 SystemVerilog 语言的 testbench 组件间通信和数据交互。
前文链接:
进程间通信
测试平台中的组件经常需要相互通信来交换数据并检查设计的输出值。下表显示了一些允许组件或线程影响数据流控制的机制。
何为事件 ?
事件(event)是同步两个或者多个不同进程的方式。一个进程等待事件产生,另一个进程会触发事件。当事件触发后 等待事件的进程 将继续执行。
1、用 event 创建事件
event event_name ;
2、用 -> 运算符触发事件
-> event_name ;
3、等待事件产生
@event_name ; // Use "@" operator to wait for an event
wait (event_name.triggered) ; // Or use the wait statement with "eventA.triggered"
4、将事件传递参数给函数
信号量是啥 ?
假设你想在图书馆租一个房间几个小时。管理员服务台会给你一把钥匙,让你在你请求访问的时间内使用这个房间。在你完成工作后,你要把钥匙还给管理员,然后再把钥匙给想要使用同一个房间的其他人。这样就不允许两个人同时使用这个房间。在这种情况下,钥匙是信号量。
信号量用于控制对资源的访问,称为互斥体(互斥),因为一次只能有一个实体拥有信号量。
仿真log:
注意以下几点:
- 信号量的对象 key 用 new() 函数声明并创建。new() 函数内部的表达式 就是 key 的个数;
- 用关键字 get() 来获取对象,在对象 key 有效之前一直等待。(阻塞)
- 用 put() 关键字释放对象;
邮箱是啥 ?
邮箱就是两组件之间专用的数据传输通道。
例如,可以创建一个邮箱,并将句柄传递给数据生成器和驱动器。生成器可以将数据对象推入邮箱,而驱动器将能够检索分组并将信号驱动到总线上。
仿真log:
信号量
信号量就像有固定数量的密钥。使用信号量的进程必须首先从存储桶中获取密钥才能继续执行。其他进程必须等待,直到存储桶中有密钥可供它们访问。从某种意义上说,它们特别适用于互斥、对共享资源的访问控制和基本同步。
语法
semaphore [identifier_name] ;
请注意,信号量是一个内置类,因此应该像使用任何其他类对象一样使用它。它有几个方法,可以用来为该信号量对象分配键的数量,获取键并将键放入桶中。
方法
示例
仿真 log:
邮箱
SV 的邮箱是允许不同的进程在彼此之间交换数据的一种方法。它类似于一个真正的邮箱,人们可以把信件放进盒子里,稍后别人就可以取回这些信件。
SV 的邮箱被创建为具有有边界或无边界的队列大小。有界邮箱只能存储有限数量的数据,如果进程试图将更多邮件存储到完整的邮箱中,它将被挂起,直到邮箱中有足够的空间为止。但是,无限制的邮箱具有无限的大小。
有两种类型:
- 通用邮箱:可以接受所有类型的数据;
- 参数化邮箱:只能接受所声明的特定数据类型;
邮箱 vs 队列
尽管SystemVerilog邮箱的行为本质上类似于队列,但它与队列数据类型有很大的不同。简单的队列只能从前面或后面推入和弹出项。但是,邮箱是一个内置类,它使用信号量让原子控制队列中的推送和弹出。此外,您不能访问邮箱队列中的给定索引,而只能按FIFO顺序检索项。
邮箱用在何处 ?
当有多个线程并行运行并且希望共享需要一定程度的确定性的数据时,通常使用邮箱。
通用邮箱示例
在下面显示的示例中,两个进程同时处于活动状态,其中一个 initial 块将数据放入邮箱,另一个initial 块从邮箱获取数据。
请注意,两个线程之间存在竞争,第一个线程可以推入邮箱,第二个线程可以在同一增量周期内从邮箱中弹出。因此,使用num()显示的值仅在邮箱上执行下一个 get 或 put 之前有效,并且可能取决于其他方法的开始和结束时间。
邮箱的函数和方法
参数化邮箱
默认情况下,SystemVerilog邮箱是无类型的,因此可以发送和接收混合数据类型的对象。尽管这是一个很好的功能,但它可能会在仿真期间导致类型不匹配并导致错误。要将邮箱约束为接受和发送固定数据类型的对象,可以将其参数化为该特定数据类型。
示例
在下面显示的示例中,我们首先使用 typedef 构造为可以发送和接收字符串的邮箱创建别名。虽然这一步是可选的,但这是避免不同组件之间的类型不匹配而导致编码错误的好做法。考虑到Comp1 通过此邮箱向 Comp2 发送一些字符串。当然,这两个类都需要有一个邮箱句柄,它需要连接在一起,这最好是在顶层或实例化这两个类的模块上完成。
//create alias for parameterized "string"type mailbox
typedef mailbox #(string) s_mbox;
//Define a component to send messages
class comp1;
//Create a mailbox handle to put items
s_mbox names;
//Define a task to put items into the mailbox
task send ();
for (int i=0;i <3;i++)begin
string s=$sformatf ("name_%0d",i);
#1 $display ("[%et]Comp1:Put %s",$time,s);
names.put(s);
end
endtask
endclass
//Define a second component to receive messages
class comp2;
//Create a mailbox handle to receive items
s_mbox list;
//create a loop that continuously gets an item from
//the mailbox
task receive ();
forever begin
string s;
list.get(s);
$display ("[%et] Comp2:Got %s",$time,s);
end
endtask
endclass
//Connect both mailbox handles at a higher level
module tb1;
//Declare a global mailbox and create both components
s_mbox m_mbx= new();
comp1 m_comp1=new();
comp2 m_comp2 =new();
initial
begin
//Assign both mailbox handles in components with the
//global mailbox
m_comp1.names =m_mbx;
m_comp2.list =m_mbx;
//start both components,where comp1 keeps sending
//and comp2 keeps receiving
fork
m_comp1.send();
m_comp2.receive();
join
end
endmodule//create alias for parameterized "string"type mailbox
仿真输出:
匹配不同类型邮箱
让我们来看看如果将SystemVerilog邮箱参数化为不同的数据类型会发生什么。假设Comp1具有字符串类型,而Comp2具有字节类型邮箱。将上述代码部分地方做如下修改:
会出现编译错误: