转载https://www.freesion.com/article/54621185504/
目录
Semaphore 旗语
想象一个场景,当两个线程试图对同一个地址进行操作,其中一个线程进行写操作,另一个进行读操作,这将会导致不可预知的结果;这种情况下,就需要旗语就通过控制来分享资源;从概念上可以这样理解,旗语可以看做一个桶,同时该桶在创建时包含多把钥匙,要获取桶中的资源,必须首先获得一把或多把桶的钥匙,在获取完资源后必须将钥匙归还给桶;如果此时另一个线程试图获取桶中的资源,必须等到桶中有足够多的钥匙的时候才能进行,否则会一直阻塞在这里.
创建旗语的格式:
semaphore semaphore_name;
旗语是一个内建的类,它提供了一下的几种方法:
- new() - Create a semaphore with a specified number of keys 创建一个包含特定钥匙数的旗语对象
- get() - Obtain one or more keys from the bucket 从桶中获取一把或多把钥匙
- put() - Return one or more keys into the bucket 归还一把或多把钥匙到桶中
- try_get() - Try to obtain one or more keys without blocking 试图获得一把或多把钥匙而不带阻塞
new( )
创建旗语对象,语法格式如下:
semaphore_name = new(numbers_of_keys);//默认的钥匙数目为0
put( )
归还钥匙到旗语中,语法格式如下:
semaphore_name.put(number_of_keys); or semaphore_name.put();//默认的钥匙数目为1
get( )
从旗语中获得钥匙,语法格式如下:
semaphore_name.get(number_of_keys); or semaphore_name.get();//默认的钥匙数为1
如果指定的钥匙数可得,则方法返回返回值并且线程继续执行;如果指定的钥匙数目不可获得,则线程会阻塞,直至所要求的钥匙数可得.
try_get()
试图获得钥匙而避免发生阻塞,语法格式如下:
semaphore_name.try_get(number_of_keys); or semaphore_name.try_get();//默认的钥匙数为1
如果指定的钥匙数可得,则方法返回返回值1并且线程继续执行;如果指定的钥匙数目不可获得,则则方法返回返回值0.
例子:
-
module semaphore_ex;
-
semaphore sema;
//declaring semaphore sema
-
initial begin
-
sema=
new(
1);
//creating sema with '1' key 创建带一把钥匙的旗语
-
fork
-
display(
);
//process-1 创建两个线程,本来两个线程是并行,但是钥匙只有1把,所以不能并行
-
display();
//process-2
-
join
-
end
-
-
//display method
-
task automatic display();
//线程要获得旗语才能继续执行
-
sema.
get();
//getting '1' key from sema
-
$display($time,
"\tCurent Simulation Time");
-
#30;
-
sema.put();
//putting '1' key to sema
-
endtask
-
endmodule
结果:
-
module semaphore_ex;
-
semaphore sema;
//declaring semaphore sema
-
-
initial begin
-
sema=
new(
4);
//creating sema with '4' keys
-
fork
-
display(
);
//process-1//钥匙数目足够,两个线程并行执行
-
display();
//process-2
-
join
-
end
-
-
//display method
-
task automatic display();
-
sema.
get(
2);
//getting '2' keys from sema
-
$display($time,
"\tCurent Simulation Time");
-
#30;
-
sema.put(
2);
//putting '2' keys to sema
-
endtask
-
endmodule
-
module tb_top;
-
semaphore key;
// Create a semaphore handle called
"key"
-
-
initial begin
-
key = new (
1);
// Create only a single key; multiple
keys are also possible
-
fork
-
personA ();
// personA tries to get the room
and puts it back after work
-
personB ();
// personB also tries to get the room
and puts it back after work
-
#25 personA (); // personA tries to get the room a second time
-
join_none
-
end
-
-
task getRoom (bit [
1:
0] id);
-
$display (
"[%0t] Trying to get a room for id[%0d] ...", $time, id);
-
key.get (
1);
-
$display (
"[%0t] Room Key retrieved for id[%0d]", $time, id);
-
endtask
-
-
task putRoom (bit [
1:
0] id);
-
$display (
"[%0t] Leaving room id[%0d] ...", $time, id);
-
key.put (
1);
-
$display (
"[%0t] Room Key put back id[%0d]", $time, id);
-
endtask
-
-
// This person tries to get the room immediately
and puts
-
// it back
20
time units later
-
task personA ();
-
getRoom (
1);
-
#20 putRoom (1);
-
endtask
-
-
// This person tries to get the room after
5
time units
and puts it back after
-
//
10
time units
-
task personB ();
-
#5 getRoom (2);
-
#10 putRoom (2);
-
endtask
-
endmodule
结果:
Mailbox 信箱
信箱是一种允许两个线程之间进行数据交换的通信机制;一个线程要想与另一个线程进行通信,首先要将数据发送到信箱,信箱将数据临时存储在系统定义的内存对象中 ,然后传递给另一个线程.信箱与队列的不同之处在于,队列中的数据可以从头部和尾部存取,而信箱中的数据属于先进先出的类型.
一个信箱可以有容量上的限制,也可以没有;当一个发送端线程试图向一个容量固定且已满的信箱中继续存入数据的时候,会发生阻塞直至信箱中有数据被移走;同样的,如果接收端线程试图从一个已空的信箱中获得数据,它也会发生阻塞,直至信箱中有数据存入.
存在两种类型的信箱:
(1)普通信箱--可以存取任何类型的数据,定义格式如下:
mailbox mailbox_name;
(2)参数化信箱(Parameterized mailbox)-- 存取特定类型的数据
typedef mailbox#(type) mailbox_name;
信箱是一个内建的类,它提供了一下的几种方法:
new()
创建信箱对象,语法格式如下:
-
mailbox_name =
new();
// 创建一个无容量限制的信箱
-
mailbox_name =
new(m_size);
//创建一个定容信箱
put()
将数据存入信箱;
try_put()
将数据存入信箱而不发生阻塞;
get() or peek()
从信箱中获取数据;其中peek()方法用来探视信箱数据而不将数据移出信箱,而get()方法会将数据移出信箱;
try_get( )or try_peek( )
试图获得信箱数据而不发生阻塞,特性与上面一样;
num()
返回信箱中的数据条目;
例子:
-
//-------------------------------------------------------------------------
-
// Packet数据类,产生随机数
-
//-------------------------------------------------------------------------
-
class
packet;
-
rand bit [
7:
0] addr;
-
rand bit [
7:
0] data;
-
-
//Displaying randomized values
-
function void post_randomize();
-
$display(
"Packet::Packet Generated");
-
$display(
"Packet::Addr=%0d,Data=%0d",addr,data);
-
endfunction
-
endclass
-
-
//-------------------------------------------------------------------------
-
//Generator - Generates the transaction packet and send to driver
-
//-------------------------------------------------------------------------
-
class
generator;
//发送类,将数据类产生的数据存入信箱
-
packet pkt;
-
mailbox m_box;
-
//constructor, getting mailbox handle
-
function new(mailbox m_box);
//发送类与接收类的信箱是一个,所以在构造方法中获得信箱的句柄
-
this.m_box = m_box;
-
endfunction
-
task run;
//不能在方法中实例化信箱,这样的话信箱就不止一个了
-
repeat(
2) begin
-
pkt =
new();
-
pkt.randomize();
//generating packet
-
m_box.put(pkt);
//putting packet into mailbox 将数据发送到信箱
-
$display(
"Generator::Packet Put into Mailbox");
-
#5;
-
end
-
endtask
-
endclass
-
-
//-------------------------------------------------------------------------
-
// Driver - Gets the packet from generator and display's the packet items
-
//-------------------------------------------------------------------------
-
class
driver;
//驱动类或接收类,获得信箱中的数据
-
packet pkt;
-
mailbox m_box;
-
-
//constructor, getting mailbox handle
-
function new(mailbox m_box);
//构造函数获得的信箱句柄与发送类是一个,所以通过构造方法传递信箱的句柄
-
this.m_box = m_box;
-
endfunction
-
-
task run;
-
repeat(
2) begin
-
m_box.
get(pkt);
//getting packet from mailbox 从信箱中获得数据
-
$display(
"Driver::Packet Recived");
-
$display(
"Driver::Addr=%0d,Data=%0d\n",pkt.addr,pkt.data);
-
end
-
endtask
-
endclass
-
-
//-------------------------------------------------------------------------
-
// tbench_top
-
//-------------------------------------------------------------------------
-
module mailbox_ex;
-
generator gen;
-
driver dri;
-
mailbox m_box;
//declaring mailbox m_box
-
-
initial begin
-
//Creating the mailbox, Passing the same handle to generator and driver,
-
//because same mailbox should be shared in-order to communicate.
-
m_box =
new();
//creating mailbox 创建信箱对象,并将信箱句柄赋值
-
-
gen =
new(m_box);
//creating generator and passing mailbox handle 实例化发送类和接收类对象
-
dri =
new(m_box);
//creating driver and passing mailbox handle
-
$display(
"------------------------------------------");
-
fork
-
gen.run();
//Process-1 创建两个线程,一个发送数据到信箱,一个接收数据到信箱
-
dri.run();
//Process-2
-
join
-
$display(
"------------------------------------------");
-
end
-
endmodule
结果:
可以看出,实现了两个线程之间的数据交换.
-
// Data packet in this environment
-
class
transaction;
-
rand bit [
7:
0] data;
-
-
function display ();
-
$display (
"[%0t] Data = 0x%0h", $time, data);
-
endfunction
-
endclass
-
-
// Generator class - Generate a transaction object and put into mailbox
-
class
generator;
-
mailbox mbx;
-
-
function new (mailbox mbx);
-
this.mbx = mbx;
-
endfunction
-
-
task genData ();
-
transaction trns =
new ();
-
trns.randomize ();
-
trns.display ();
-
$display (
"[%0t] [Generator] Going to put data packet into mailbox", $time);
-
mbx.put (trns);
//将数据存入信箱
-
$display (
"[%0t] [Generator] Data put into mailbox", $time);
-
endtask
-
endclass
-
-
// Driver class - Get the transaction object from Generator
-
class
driver;
-
mailbox mbx;
-
-
function new (mailbox mbx);
-
this.mbx = mbx;
-
endfunction
-
-
task drvData ();
-
transaction drvTrns =
new ();
-
$display (
"[%0t] [Driver] Waiting for available data", $time);
-
mbx.
get (drvTrns);
//从信箱中取出数据
-
$display (
"[%0t] [Driver] Data received from Mailbox", $time);
-
drvTrns.display ();
-
endtask
-
endclass
-
-
// Top Level environment that will connect Gen and Drv with a mailbox
-
module tb_top;
-
mailbox mbx;
-
generator Gen;
-
driver Drv;
-
-
initial begin
-
mbx =
new ();
-
Gen =
new (mbx);
-
Drv =
new (mbx);
-
-
fork
-
#10 Gen.genData ();
-
Drv.drvData ();
-
join_none
-
end
-
endmodule
结果:
-
module tb;
-
// Create a new mailbox that can hold utmost 2 items
-
mailbox mbx =
new(
2);
-
-
// Block1: This block keeps putting items into the mailbox
-
// The rate of items being put into the mailbox is 1 every ns
-
initial begin
-
for (int i=
0; i <
5; i++) begin
-
#1 mbx.put (i);
-
$display (
"[%0t] Thread0: Put item #%0d, size=%0d", $time, i, mbx.num());
-
end
-
end
-
-
// Block2: This block keeps getting items from the mailbox
-
// The rate of items received from the mailbox is 2 every ns
-
initial begin
-
forever begin
-
int idx;
-
#2 mbx.get (idx);
-
$display (
"[%0t] Thread1: Got item #%0d, size=%0d", $time, idx, mbx.num());
-
end
-
end
-
endmodule
结果:
-
// 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 (
"[%0t] 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 (
"[%0t] Comp2: Got %s", $time, s);
-
end
-
endtask
-
endclass
-
-
// Connect both mailbox handles at a higher level
-
module tb;
-
// 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
-
typedef mailbox #(
string) s_mbox;
-
-
// Define a component to send messages
-
class comp1;
-
-
// Create a mailbox handle to put items
-
s_mbox names;
//信箱为String类型
-
-
// 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 (
"[%0t] 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
-
mailbox #(byte)
list;
//信箱为byte类型
-
-
-
// Create a loop that continuously gets an item from
-
// the mailbox
-
task receive ();
-
forever begin
-
string s;
-
list.get(s);
-
$display (
"[%0t] Comp2: Got %s", $time, s);
-
end
-
endtask
-
endclass
-
-
// Connect both mailbox handles at a higher level
-
module tb;
-
// Declare a global mailbox and create both components
-
s_mbox m_mbx =
new();
//信箱为String类型
-
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;
//将String类型信箱的句柄赋给byte类型
-
-
// Start both components, where comp1 keeps sending
-
// and comp2 keeps receiving
-
fork
-
m_comp1.send();
-
m_comp2.receive();
-
join
-
end
-
endmodule
结果:
由于信箱类型不匹配,导致报错.
Event 事件
事件是静态对象,用于线程之间的同步 ;
一个线程可以触发一个事件,另一个线程可以等待另一个事件的触发;事件触发使用"->"或"->>"操作符,等待事件的触发可以使用"@"或"wait()"操作;
SV中的事件可以作为同步线程的句柄,可以传递给方法,这个特点使得可以在对象之间共享事件,而不用把事件定义成全局变量;
-> operator
触发一个事件,同时也会触发等待该事件的线程;
->> operator
触发非阻塞事件;
@ operator
等待一个事件被触发,语法格式如下:
@(event_name.triggered);//等待事件的触发并阻塞线程,边沿敏感
对于一个触发接触解除一个正在等待事件的线程的阻塞,阻塞的线程必须在时间触发操作->之前执行@语句.如果事件的触发在@语句之前,则线程会继续阻塞.
wait operator
如果一个事件的触发与等待该事件的@操作符的语句在同一时刻发生,由于@是边沿敏感的,@操作符可能会错过事件的触发,这时候可以用电平敏感的wait()方法来替代@操作符,语法格式如下:
wait(event_name.triggered);
wait_order()
wait_oeder()会阻塞线程的进行,直至括号的的事件按给定的顺序触发,如果括号中的事件没有按给定的顺序触发,也会阻塞线程的进行。
例子:
-
wait_order(a,b,
c);
//阻塞线程,直至事件以a->b->c的顺序依次触发
-
//如果不是这个触发顺序,则会产生错误
-
-
wait_order( a, b,
c )
else $display(
"Error: events out of order" );
-
//用一条失败的语句代替了错误的产生,如果不是a->b->c的顺序触发,则会输出"Error: events out of order"
-
-
bit success;
-
wait_order( a, b,
c ) success =
1;
else success =
0;
-
-
//用变量输出代替了上面的输出语句
Merging events
如果将一个事件赋给另外一个事件,那么触发两个事件中的任意一个,另一个事件都会触发.
看下面例子:
-
module events_ex;
-
event ev_1;
//定义 event ev_1
-
-
initial begin
-
fork
-
//process-1, triggers the event
-
begin
-
#40;
-
$display($time,
"\tTriggering The Event");
-
->ev_1;
//触发事件
-
end
-
-
//process-2, wait for the event to trigger
-
begin
-
$display($time,
"\tWaiting for the Event to trigger");
-
@(ev_1.triggered);
//等待事件触发
-
$display($time,
"\tEvent triggered");
-
end
-
join
-
end
-
endmodule
输出:
-
module events_ex;
-
event ev_1;
//declaring event ev_1
-
-
initial begin
-
fork
-
//process-1, triggers the event
-
begin
-
#40;
-
$display($time,
"\tTriggering The Event");
-
->ev_1;
//事件在40ns被触发
-
end
-
-
//process-2, wait for the event to trigger
-
begin
-
$display($time,
"\tWaiting for the Event to trigger");
-
#60;//等待的语句却在60ns
-
@(ev_1.triggered);
-
$display($time,
"\tEvent triggered");
-
end
-
join
-
end
-
initial begin
-
#100;
-
$display($time,
"\tEnding the Simulation");
//事件触发在等待语句之前,所以事件不会触发
-
$finish;
-
end
-
endmodule
结果:
当事件与等待语句在同一时刻进行时,wait()因为是属于电平敏感的语句,所以会检测到事件的触发;而@操作符是边沿敏感的,所以检测不到事件的触发.看下面两个例子:
-
module events_ex;
-
event ev_1;
//declaring event ev_1
-
-
initial begin
-
fork
-
//process-1, triggers the event
-
begin
-
$display($time,
"\tTriggering The Event");
-
->ev_1;
-
end
-
-
//process-2, wait for the event to trigger
-
begin
-
$display($time,
"\tWaiting for the Event to trigger");
-
wait(ev_1.triggered);
-
$display($time,
"\tEvent triggered");
-
end
-
join
-
end
-
endmodule
可以看到wait()检测到了事件的触发,线程继续往下执行;
-
module events_ex;
-
event ev_1;
//declaring event ev_1
-
-
initial begin
-
fork
-
//process-1, triggers the event
-
begin
-
$display($time,
"\tTriggering The Event");
-
->ev_1;
-
end
-
-
//process-2, wait for the event to trigger
-
begin
-
$display($time,
"\tWaiting for the Event to trigger");
-
@(ev_1.triggered);
-
$display($time,
"\tEvent triggered");
-
end
-
join
-
end
-
endmodule
而@却错过了事件的触发,导致线程继续阻塞;
下面来看wait_order()的用法:
-
module events_ex;
-
event ev_1;
//declaring event ev_1
-
event ev_2;
//declaring event ev_2
-
event ev_3;
//declaring event ev_3
-
-
initial begin
-
fork
-
//process-1, triggers the event ev_1
-
begin
-
#6;
-
$display($time,
"\tTriggering The Event ev_1");
-
->ev_1;
-
end
-
//process-2, triggers the event ev_2
-
begin
-
#2;
-
$display($time,
"\tTriggering The Event ev_2");
-
->ev_2;
-
end
-
//process-3, triggers the event ev_3
-
begin
-
#8;
-
$display($time,
"\tTriggering The Event ev_3");
-
->ev_3;
-
end
-
//process-4, wait for the events to trigger in order of ev_2,ev_1 and ev_3
-
begin
-
$display($time,
"\tWaiting for the Event's to trigger");
-
wait_order(ev_2,ev_1,ev_3)
//注意事件触发顺序
-
$display($time,
"\tEvent's triggered Inorder");
-
else
-
$display($time,
"\tEvent's triggered Out-Of-Order");
-
end
-
join
-
end
-
endmodule
事件触发以正常的顺序进行,没有输出错误提示语句;
-
module events_ex;
-
event ev_1;
//declaring event ev_1
-
event ev_2;
//declaring event ev_2
-
event ev_3;
//declaring event ev_3
-
-
initial begin
-
fork
-
//process-1, triggers the event ev_1
-
begin
-
#6;
-
$display($time,
"\tTriggering The Event ev_1");
-
->ev_1;
-
end
-
-
//process-2, triggers the event ev_2
-
begin
-
#2;
-
$display($time,
"\tTriggering The Event ev_2");
-
->ev_2;
-
end
-
-
//process-3, triggers the event ev_3
-
begin
-
#1;
-
$display($time,
"\tTriggering The Event ev_3");
-
->ev_3;
-
end
-
//process-4, wait for the events to trigger in order of ev_2,ev_1 and ev_3
-
begin
-
$display($time,
"\tWaiting for the Event's to trigger");
-
wait_order(ev_2,ev_1,ev_3)
-
$display($time,
"\tEvent's triggered Inorder");
-
else
-
$display($time,
"\tEvent's triggered Out-Of-Order");
-
end
-
join
-
end
-
endmodule
事件触发为乱序,导致输出了错误提示语句;
-
module tb_top;
-
event eventA;
// Declare an event handle called "eventA"
-
-
initial begin
-
fork
-
waitForTrigger (eventA);
// Task waits for eventA to happen
-
#5 ->eventA; // Triggers eventA
-
join
-
end
-
-
// The event is passed as an argument to this task. It simply waits for the event
-
// to be triggered
-
task waitForTrigger (event eventA);
-
$display (
"[%0t] Waiting for EventA to be triggered", $time);
-
wait (eventA.triggered);
-
$display (
"[%0t] EventA has triggered", $time);
-
endtask
-
endmodule
上面的例子将事件作为方法参数传递给方法,输出结果如下:
SV的调度语义(Scheduling Semantics)
本节概述一下SV中的元素之间的调度和行为,特别是事件的调度和执行.
IEEESTD 1800-20051标准将SV的时隙划分为17个有序区域:9个执行SystemVerilog语句的有序区域和8个执行PLI代码的有序区域。将时隙划分为这些有序区域的目的是在设计和测试平台代码之间提供可预测的交互.
事件调度的使用
所模拟的系统描述中的线网或变量状态的每一次改变都被视为更新事件,当执行更新事件时,所有对这些事件敏感的线程都被视为评估事件.这些线程包括 initial, always, always_comb, always_latch, always_ff等过程语句块、以及连续赋值、异步任务和程序赋值语句等等.
单个时隙被划分为可以调度事件的多个区域。此事件调度支持获得明确和可预测的交互,从而为特定类型的执行提供排序。
这允许属性和检查器在被测试的设计处于稳定状态时对数据进行采样。属性表达式可以安全地进行评估,并且Testbench可以一种可预测的方式对属性和检查器作出零延迟的反应.
注意:
(1)术语仿真时间是指仿真器持续运行的时间值,用于模拟系统描述所需的实际时间;
(2)一个时隙包括在每个仿真时间SV的事件区域中在事件区域中处理的所有模拟活动,新SV事件区域被发展以支持新的SV构建体,并且还防止在RTL设计和新的验证构造之间产生竞争条件
这些新区域保证了设计、测试工作台和断言之间的可预测性和一致性.
下面分别介绍这些区域:
(1)preponed区域
并发断言中使用的变量值在这个区域进行采样(在observe区域进行评估),prepone区域在每个时隙中只执行一次,并且在推进模拟时间后立即执行。
(2)Pre-active 区域
该区域专门用于PLI回调控制点 ,PLI回调控制点允许在事件在active区域评估之前使用户代码能够读写数值并创建事件.
(3)Active 区域
保存正在评估的当前事件,并且可以按照任何顺序进行处理.
- 执行所有的模块阻塞赋值;
- 评估所有非阻塞赋值的右侧(Rignt-Hand-side),并将更新安排到NBA区域;
- 执行所有模块的连续赋值;
- 评估输入和更新verilog原语的输出;
- 执行$display和$finish命令
(4)Inactive 区域
在所有有效事件被处理后保存要评估的事件,在这个区域#0的阻塞延时被安排;
(5)Pre-NBA 区域
该区域专门用于PLI回调控制点 ,PLI回调控制点允许在事件在NBA区域评估之前使用户代码能够读写数值并创建事件.
(6)Non-blocking Assignment Events(NBA) 区域
该区域的主要作用是执行对左边(left-hand-side)变量的更新,这些变量是指当前正在执行的所有非阻塞赋值并且在活动区域中调度的变量 .
(7)Post-NBA 区域
该区域专门用于PLI回调控制点 ,PLI回调控制点允许在事件在NBA区域评估之后使用户代码能够读写数值并创建事件.
(8)Observed 区域
该区域的主要功能是评估在preponed区域使用采样值的并发断言.评估的标准是特性评估在每个时隙只进行一次.在评估期间,通过和失败的代码必须在当前时隙的Reactive区域被安排
(9)Post-observed 区域
该区域专门用于PLI回调控制点 ,PLI回调控制点允许用户代码在特性评估(在observe区域或更早)之后能够读写数值并创建事件.
(10)Reactive 区域
程序块中指定的代码和属性表达式中的传递/失败代码都在反应性区域中调度,该区域的主要作用是以任意顺序评估和执行所有当前的程序活动
- 执行所有程序块的赋值;
- 执行来自并发断言的通过和失败的代码;
- 评估所有非阻塞赋值块的右边(Right-hand-side);
- 执行所有代码的连续赋值语句;
- 执行$exit和隐式$exit命令;
(11)Re-Inactive Events 区域
在此区域中,程序进程中的#0阻塞被安排.
(12)Postponed Region
执行$strobe和$monitor命令显示当前时隙的最终的更新值 ,此区域还用于收集使用选通抽样的项目的功能覆盖范围.
Program Block
module是设计的基本结构,module中可以包括多个过程块。program结构提供了design与testbench之间的无竞争交互,program中声明的所有元素都将在Reactive区域执行。
module中的非阻塞赋值在active区域被安排,program中的initial块在reactive区域安排。
program中的语句(在Reactive区域安排)对设计模块中的信号(在active区域安排)变化非常敏感,因为active区域在Reactive区域前面,这样可以避免design和testbench之间的竞争.
program块的语法格式如下:
-
program test (input clk, input [
7:
0] addr, output [
7:
0] wdata);
-
...
-
endprogram
-
-
or
-
-
program test (
interface mem_intf);
-
...
-
endprogram
对于program块:
- 可以实例化,端口可以与module连接;
- 可以拥有多个initial块;
- 不能够包含always、module、interface和其他program块;
- 在program块中,变量只能使用阻塞赋值,使用非阻塞赋值会报错;
下面的例子将展示用module和program两种不同的块来写testbench之间的不同:
-
//-------------------------------------------------------------------------
-
//design code
-
//-------------------------------------------------------------------------
-
module design_ex(output bit [7:0] addr);
-
initial begin
-
addr <=
10;
-
end
-
endmodule
-
-
//-------------------------------------------------------------------------
-
//testbench
-
//-------------------------------------------------------------------------
-
module testbench(input bit [7:0] addr);
-
initial begin
-
$display(
"\t Addr = %0d",addr);
-
end
-
endmodule
-
-
//-------------------------------------------------------------------------
-
//testbench top
-
//-------------------------------------------------------------------------
-
module tbench_top;
-
wire [
7:
0] addr;
-
-
//design instance
-
design_ex dut(addr);
//由于两个module之间同时对一个变量进行操作,产生了竞争
-
-
//testbench instance
-
testbench test(addr);
-
endmodule
由于两个module之间的竞争,addr的数据结果为默认值.
-
//-------------------------------------------------------------------------
-
//design code
-
//-------------------------------------------------------------------------
-
module design_ex(output bit [7:0] addr);
-
initial begin
-
addr <=
10;
-
end
-
endmodule
-
-
//-------------------------------------------------------------------------
-
//testbench
-
//-------------------------------------------------------------------------
-
-
program testbench(input bit [7:0] addr);
-
initial begin
-
$display(
"\t Addr = %0d",addr);
-
end
-
endprogram
-
-
//-------------------------------------------------------------------------
-
//testbench top
-
//-------------------------------------------------------------------------
-
module tbench_top;
-
wire [
7:
0] addr;
-
-
//design instance
-
design_ex dut(addr);
-
-
//testbench instance
-
testbench test(addr);
-
endmodule
module块在active区域执行,而program块在Reactive执行,也就是说program块在module块之后执行,所以即使对同一变量操作两者之间也不会产生竞争,所以输出正确值。结果如下:
还有一点需要声明的是,program中可以调用module中的方法,但是module中不能调用program中的方法.
Interface 接口
接口用来连接testbench和design。对比下面两个图:
左边的图为不带接口进行连接的情况,右边的图为两者用接口连接的情况.
接口可以认为是一捆智能的连线,包含了连接、同步、多个块之间的通信功能.
一个接口必须明确:
(1)信号的方向(默认为input),用modport关键字;
(2)时钟信息,采用时钟块clocking block;
接口可以包含常数、变量、方法;
下面是一个接口定义的格式:
-
interface interface_name;
-
...
-
interface_items
//接口中的类型一般定义为logic,它既可以在过程块中赋值,也可以用连续语句赋值
-
//logic类型只能有单个驱动
-
...
-
endinterface
接口可以像类或module那样实例化,格式:
interface_name inst_name;
与传统端口连接方式相比,接口的好处有:
(1)将多个信号组合成一捆信号,将多个端口的连接变成一个接口的连接;
(2)接口一旦被定义,就可以通过例化接口来实现模块之间的连接;
(3)添加或删除一个信号很容易;
(4)接口中可以包含task、function、parameter、变量、function coverage、assertion。
-
//-------------------------------------------------------------------------
-
// Verilog Design
-
//-------------------------------------------------------------------------
-
module memory ( clk, addr, wr_rd, wdata, rdata);
-
input clk;
-
input [
7:
0] addr;
-
input wr_rd;
-
input [
7:
0] wdata;
-
output [
7:
0] rdata;
-
endmodule
-
-
//-------------------------------------------------------------------------
-
// Interface
-
//-------------------------------------------------------------------------
-
interface mem_intf;
//定义接口
-
logic clk;
-
logic [
7:
0] addr;
-
logic wr_rd;
-
logic [
7:
0] wdata;
-
logic [
7:
0] rdata;
-
endinterface
-
-
//-------------------------------------------------------------------------
-
// TestCase
-
//-------------------------------------------------------------------------
-
program testcase (interface intf);
//接口作为参数
-
environment env;
-
initial begin
-
env =
new(intf);
-
end
-
……
-
endprogram
-
-
//-------------------------------------------------------------------------
-
// TestBench Top
-
//-------------------------------------------------------------------------
-
module tbench_top;
-
//Interface instance
-
mem_intf intf();
//实例化接口
-
-
//DUT(Design Under Test) instance
-
memory dut(
-
.clk(intf.clk),
//引用接口中的变量
-
.addr(intf.addr),
-
.wr_rd(intf.wr_rd),
-
.wdata(intf.wdata),
-
.rdata(intf.rdata)
-
);
-
-
//TestCase Instance
-
testcase test(intf);
-
……
-
endmodule
modport
将接口中的信号端口分组,并明确端口的方向;通过明确了信号的方向,modport提供了访问该信号的限制.例如,modport将信号方向定义为input,则任何企图给该信号赋值的操作都是非法的.modport中可以定义信号的方向包括:
- inout
- output
- input
- ref
下面举个例子来说明modport的用法:
-
interface my_intf;
-
logic a;
-
logic b;
-
-
//modport delcaration
-
modport driver (input a, output b);
//1个modport中不需要包含所有的信号
-
modport monitor (input a, input b);
-
-
endinterface
一个接口可以包含多个modport关键字,一个信号可以存在多个modport中。如果需要引用上例中的driver,首先需要实例化接口,然后再调用接口中的diver,格式如下:
-
my_intf.driver intf1;
-
-
//通用格式
-
interface_name.modport_name 接口实例名;
下面例子展示了如何将接口连接到DUT:
-
// Filename : myBus.sv
-
//定义接口
-
interface myBus (input clk);
//接口可以带参数,参数一般是clk和rst
-
logic [
7:
0]
data;
-
logic enable;
-
-
// From TestBench perspective, 'data' is input and 'write' is output
-
modport TB (input
data, output enable);
-
-
// From DUT perspective, 'data' is output and 'enable' is input
-
modport DUT (output
data, input enable);
-
endinterface
-
-
//定义DUT
-
// Filename : dut.sv
-
module dut (myBus.DUT busIf);
//注意DUT与接口的连接格式:接口名.modport名 接口实例名
-
//其实就是创建了一个接口句柄,只不过在接口名后要加modport名
-
always @ (posedge clk)
-
if (busIf.enable)
//引用接口中的属性与引用对象属性是一样的 接口名.属性名
-
busIf.
data <= busIf.
data+
1;
-
else
-
busIf.
data <=
0;
-
endmodule
-
-
//在顶层模块中将DUT与接口连接
-
// Filename : tb_top.sv
-
module tb_top;
-
bit clk;
-
bit enable;
-
-
// Create a clock
-
always #
10 clk = ~clk;
-
-
// Create an interface object
-
myBus busIf (clk);
//实例化接口对象
-
-
// Instantiate the DUT; pass modport DUT of busIf
-
dut dut0 (busIf.DUT);
//将接口对象作为参数传递给DUT 实例名.modport名
-
-
// Testbench code : let's wiggle enable
-
initial begin
-
enable <=
0;
-
#
10 enable <=
1;
-
#
40 enable <=
0;
-
#
20 enable <=
1;
-
end
-
endmodule
parameterize an interface 在接口中使用常数(parameter)
-
interface myBus #(parameter D_WIDTH=
31) (input clk);
//这与常数化module的格式是一样的
-
logic [D_WIDTH
-1:
0]
data;
-
logic enable;
-
endinterface
Task and Function in interface
接口中可以包含方法,如下:
-
interface task_function_if (input clk);
-
logic req;
-
logic gnt;
-
//=================================================
-
// Clocking block for tb modport
-
//=================================================
-
clocking cb @ (posedge clk);
-
input gnt;
-
output req;
-
endclocking
-
//=================================================
-
// Task inside a interface, You can basically
-
// Write a BFM, who's input can be a class or
-
// a mailbox, so on..... SO COOL!!!!
-
//=================================================
-
task drive_req (input integer delay, input bit value);
-
$display(
"@%0dns Before driving req %b from task at %m", $time, req);
-
repeat (delay) @ (posedge clk);
-
req = value;
-
$display(
"@%0dns Driving req %b from task at %m", $time, req);
-
endtask
-
//=================================================
-
// Mod port with exporting and importing tasks
-
//=================================================
-
modport dut (input clk,req, output gnt
-
//, export print_md, import print_tb
-
);
-
//=================================================
-
// Mod Port with exporting and importing tasks
-
//=================================================
-
modport tb (clocking cb, input clk,
-
import drive_req
//导入方法
-
//, print_md, export print_tb
-
);
-
-
endinterface
Clocking Block 时钟块
时钟块明确了一组信号的时序和同步.定义一个时钟块必须明确:
- 时钟事件用作design和testbench之间的同步参考;
- 被testbench驱动和采样的一系列信号;
- 与时钟事件有关的时钟,testbench利用这个时钟来采样和驱动信号;
- 一个接口中可以包括多个时钟块;
时钟块可以定义在接口、module和prgram块中.
下面举例:
-
clocking cb @(posedge clk);
-
default input
#1 output
#2;
//输入输出的时钟偏斜
-
input from_Dut;
//也就是说输入信号的采样在时钟沿的前1ns,输出信号的驱动在时钟沿之后的2ns
-
output to_Dut;
-
endclocking
Clocking event:时钟事件 时钟事件用来同步时钟块,如上面的@(posedge clk);
Clocking signal: 时钟信号 信号的采样和驱动在时钟块完成,从DUT到DUT是利用时钟信号完成的;
Clocking skew: 时钟偏斜 上面例子中#1和#2就是时钟偏斜,时钟偏斜明确了输入和输出的时钟信号被采样和驱动的时刻,时钟偏斜可以是常数,也可以是常量表达式;
下面具体讲一下时钟偏斜:
理想情况下信号是在时钟事件(也就是时钟沿)被采样或驱动的,但是如果定义了一个输入偏斜,那么信号的采样就会在边沿之前的偏斜时钟位置采样,对于输出信号就会在边沿之后的时钟偏斜位置被驱动。如果时钟偏斜量没有带单位,则会使用是时钟信号的单位。看下面例子:
-
clocking cb @(posedge clk);
-
default input
#1ps output
#2;
-
input from_Dut;
-
output to_Dut;
-
endclocking
-
-
//也可以将偏斜明确到某个具体的信号
-
-
clocking cb @(clk);
-
input
#4ps from_Dut;
-
output
#6 to_Dut;
-
endclocking
无论用于声明时钟块的实际时钟事件是什么,都可以通过使用时钟块名称直接获得时钟块的时钟事件.也就是说,对于上面例子,@(cb)和@(posedge clk)是等效的.
-
clocking ck1 @ (posedge clk);
-
default input
#5ns output
#2ns;
-
input data, valid, ready = top.ele.ready;
-
output negedge grant;
//grant信号有自己的延时要求,只在下降沿被驱动
-
input
#1step addr;
//在上升沿到来之前的瞬间addr被采样
-
input
#1 output
#3 sig;
//sig是一个inout信号,表明在上升沿之前的1ns采样,在上升沿之后的3ns被驱动
-
endclocking
Cycle delay: ## 周期延时操作符
##操作符可以通过明确延时周期来执行延时操作,例子如下:
-
## 8; // wait 8 clock cycles
-
## (a + 1); // wait a+1 clock cycles
-
initial begin
-
// Init the outputs
-
ram.addr <=
0;
-
ram.din <=
0;
-
ram.ce <=
0;
-
ram.we <=
0;
-
// Write Operation to Ram
-
for (
int i =
0; i <
2; i++) begin
-
// 等效于 repeat (2) @ (posedge clk);
-
## 2 ;
-
ram.addr <= i;
-
ram.din <= $random;
-
ram.ce <=
1;
-
ram.we <=
1;
-
## 2;
-
ram.ce <=
0;
-
end
-
// Read Operation to Ram
-
for (
int i =
0; i <
2; i++) begin
-
// 等效于 @ (posedge clk);
-
## 1 ;
-
ram.addr <= i;
-
ram.ce <=
1;
-
ram.we <=
0;
-
// Below line is same as repeat (3) @ (posedge clk);
-
## 3;
-
ram.ce <=
0;
-
end
-
#40 $finish;
-
end
下面对上面所讲举一个完整的例子:
-
interface arb_if(input clk);
-
logic [
1:
0] grant,request;
-
logic rst;
-
-
//定义时钟块
-
clocking cb @(posedge clk)
-
output request;
-
input grant;
-
endclocking
-
//定义modport
-
-
modport TEST(clocking cb,output rst);
//使用cb
-
-
modport DUT(input request,input rst,output grant);
-
endinterface
-
-
//简单的使用接口的测试平台
-
-
module test(arb_if.TEST arbif);
-
initial begin
-
arbif.cb.request<=
0;
-
@arbif.cb;
-
$display(
"grant=%b",arbif.cb.grant);
-
end
-
endmodule
Virtual Interface 虚接口
一个虚接口就是一个变量,该变量代表了接口的一个实例,用关键字virtual来定义,定义格式如下:
virtual interface_name instance_name;
- 虚接口在使用前必须初始化,并且虚接口必须连接或指向一个实际的接口(driver中的虚接口);
- 访问为初始化的虚接口会报错;
- 虚接口可以定义为类的属性,因此可以在过程语句或构造函数new()中初始化;
- 虚接口变量可以作为参数被传递给方法;
- 所有的接口方法都可以通过虚接口句柄来访问;
虚接口的出现是为了在driver和monitor中获得接口的实例,但是接口不能在类中实例化(只能在module和program中),这是因为接口都是static的,而类是dynamic的,所以类中不允许存在接口只能存在虚接口!由于虚接口实际上是接口的实例,所以它是以成员的形式存在于类中的.