semaphore
在systemverilog中,信号量是内建的类,用于控制共享资源的访问和基本的同步。信号量像一个装有很多钥匙的桶。使用信号量的进程必须先从桶中获得一个钥匙,才可以继续执行,其它进程必须等待合适数量的钥匙回到桶中。
假设一种情况,两个进程都在尝试着访问一个共享的存储区域,也许一个进程尝试取写这块内存区域,而另一个进程尝试读这块内存区域,这样会导致一个不可预知的结果。因此需要使用信号量来克服或者避免这种情况。
信号量提供如下方法:
new()
semaphore_name = new(number_of_keys)
- 创建一个拥有number_of_keys的信号量在一个桶中,这里的number_of_keys是一个integer类型的变量。
- 默认key的数量是0.
- 这个构造函数将会返回一个信号量实例或者是一个NULL(当这个信号量没有被创建成功)
put():
这个方法用来返还信号量中keys。使用方法如下:
semaphore_name.put(number_of_keys); or semaphore_name.put();
当调用semaphore_name.put()方法时,会返回number_of_keys个keys给信号量,默认归还key的数量时1.
get()
get()方法用来从信号量中获取keys。使用方法如下:
semaphore_name.get(number_of_keys); or semaphore_name.get();
- 如果能获得指定数量的Keys,则该方法执行完成后,将会继续执行后面的语句。
- 若没有获得指定数量的Keys,那么这个进程将会被堵塞住,直到获得指定数量的Keys.
- 默认获得Key的数量是1.
try_get()
try_get()方法用来从一个信号量获取指定数量的Keys,但是不会被堵塞。使用方法如下:
semaphore_name.try_get(number_of_keys); or semaphore_name.try_get();
- 若可以获得指定数量的keys,那么当该方法执行完成之后,将会继续执行后面的语句。
- 若没有获得指定数量的语句,那么这个方法将会放回0,并且还会继续执行的后面的语句。
- 默认获得的信号量的key的数量是1。
例子1:
module semaphore_ex;
semaphore sema; //declaring semaphore sema
initial begin
sema = new(1) ; //creating sema with '1' key
fork
display(); //process-1
display(); //process-2
join
end
//display method
task automatic display();
sema.get(); //getting '1' key from sema
$display($time, "\t Current Simulation Time");
#30;
sema.put(); //putting '1' key to sema
endtask
endmodule
在上面例子中,fork join理论上并行执行,但总会有其中一个进程通过get方法获得一个key,从而会阻塞另一个进程。另一个进程只有当第一个进程把key归还才可以继续执行。其执行结果如下:
例子2
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
display(); //process-3
join
end
//display method
task automatic display();
sema.get(2); //getting '2' key from sema
$display($time,"\tCurrent Simulation Time");
#30;
sema.put(2); //putting '2' key
endtask
endmodule
上面例子中,开启三个进程,每个进程先获取两个key,然后等待30个时间范围返回2个key。但是由于key的总量是4,因此必然只有两个进程获取key,另外一个进程必须等待其它进程归还足够量的key。其执行结果如下:
例子3:
module semaphore_ex;
semaphore sema; //declaring semaphore sema
initial begin
sema = new(1); //creating sema with '1' key
fork
display(1); //process-1
display(2); //process-2
display(3); //process-3
join
end
//display method
task automatic display(int key);
sema.get(key); //getting 'key' number of keys from sema
$display($time,"\tCurrent Simulation Time,Got %0d keys",key);
#30;
sema.put(key+1); //putting 'key' number of keys to sema
endtask
endmodule
上面这个例子中,display操作传入的是进程获得key的数目,创建实例sema时共有一个Key,但是每个进程都会多还一个key。所以我们可以通过input方法,可以增加信号量实例的key的数目。
mailbox
邮箱是一个沟通机制,允许信息在不同的进程之间相互交换。发送方向将数据传至信箱,临时存储于系统定义的存储对象memory object,然后在传递给接收方。可以联想实际的邮件。
根据信箱的大小,把信箱分为有界信箱(bounded mailbox)和无界信箱(unbounded mailbox)。有界信箱的大小是给定的,当达到一个上界时,那么信箱就会满。一个进程如果想向一个满的信箱发送消息,那么这个进程将会被挂起,知道有足够的空间存放它上传的信息。无界邮箱则是没有限定信箱的存储空间大小。
根据邮箱的类型划分,可以划分为通用信箱(generic mailbox)和参数化信箱(parameterized mailbox)。
通用邮箱也叫做无类型信箱,即是一个信箱可以收发任意类型的数据,声明形式如下:
mailbox mailbox_name;
参数化信箱用来传输特定类型的数据,其声明形式如下:
mailbox #(type) mailbox_name;
下面列举出mailbox提供的方法:
new(); - Create a mailbox
put(); - Place a message in a mailbox
try_put(); - Try to place a message in a mailbox without blocking
get(); or peek();- Retrieve a message from a mailbox
num(); - Returns the number of messages in the mailbox
try_get(); or try_peek(); - Try to retrieve a message from a mailbox without blocking
new():
邮箱是通过new方法创建的,可以加入参数设置信箱大小,不传入参数则默认信箱的大小为1。
mailbox_name = new(); // Creates unbounded mailbox and returns mailbox handle
mailbox_name = new(m_size); //Creates bounded mailbox with size m_size and returns mailbox handle ,where m_size is integer variable
put()
:该方法把传入的参数存储于信箱中,信箱的数据采用fifo顺序,注意当有界信箱满了,该方法将会等待信箱由足够空间才会返回
try_put
try_put:该方法与put类似,区别是信箱满时,该方法返回0,如果不满则返回正数
get()
get:该方法从信箱中取出数据,取出的数据会移除信箱。信箱空时,该方法会等待有数据存入信箱,如果要取出得message类型和信箱存储数据类型不一致,会报运行时run-time错误
num
num:该方法返回邮箱中信息message的数目
例子
//-------------------------------------------------------------------
//Packet
//-------------------------------------------------------------------
class packet;
rand bit [7:0] addr;
rand bit [7:0] data;
//display randomized values
function void post_randomize();
$display("Packet::Packet Generated");
$display("Packet::Addr=%0d,Data=%0d", addr, data);
endfunction
endclass
//-------------------------------------------------------------------
//Generator - Generate the transaction packet and send to driver
//-------------------------------------------------------------------
class generator ;
packet pkt;
mailbox m_box;
//construction,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, generating 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 Recieved");
$display("Driver:Addr=%0d Data=%0d",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 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
//-------------------------------------------------------------------
fork
gen.run(); //process-1
dri.run(); //process-2
join
//-------------------------------------------------------------------
end
endmodule
在上面例子中,类packet拥有两个随机变量,且在类中定义了回调函数post_randomize函数来打印随机变量的值。类generator拥有一个packet句柄和一个packet句柄,构造函数new传入mailbox句柄,给句柄赋值给类内mailbox句柄,此外还定义了任务run,重复两次创建packet句柄,随机化,将句柄送入信箱。类driver于类generator的结构相似,只不过类dirive的run任务是定义是从有信箱中取出pkt。在module中,先创建信箱句柄,然后将句柄传入generator和driver类的构造函数中,通过fork-join开辟两个类的run进程。执行结果如下所示: