SV之IPC(INTER PROCESS COMMUNICATION)线程间的通信机制

转载https://www.freesion.com/article/54621185504/

目录

 

Semaphore  旗语

Mailbox 信箱

 Event  事件

 SV的调度语义(Scheduling Semantics) 

Program Block

Interface 接口 

 Virtual Interface 虚接口


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.

例子:


  
  
  1. module semaphore_ex;
  2. semaphore sema; //declaring semaphore sema
  3. initial begin
  4. sema= new( 1); //creating sema with '1' key 创建带一把钥匙的旗语
  5. fork
  6. display( ); //process-1 创建两个线程,本来两个线程是并行,但是钥匙只有1把,所以不能并行
  7. display(); //process-2
  8. join
  9. end
  10. //display method
  11. task automatic display(); //线程要获得旗语才能继续执行
  12. sema. get(); //getting '1' key from sema
  13. $display($time, "\tCurent Simulation Time");
  14. #30;
  15. sema.put(); //putting '1' key to sema
  16. endtask
  17. endmodule

结果:

 


  
  
  1. module semaphore_ex;
  2. semaphore sema; //declaring semaphore sema
  3. initial begin
  4. sema= new( 4); //creating sema with '4' keys
  5. fork
  6. display( ); //process-1//钥匙数目足够,两个线程并行执行
  7. display(); //process-2
  8. join
  9. end
  10. //display method
  11. task automatic display();
  12. sema. get( 2); //getting '2' keys from sema
  13. $display($time, "\tCurent Simulation Time");
  14. #30;
  15. sema.put( 2); //putting '2' keys to sema
  16. endtask
  17. endmodule


  
  
  1. module tb_top;
  2. semaphore key; // Create a semaphore handle called "key"
  3. initial begin
  4. key = new ( 1); // Create only a single key; multiple keys are also possible
  5. fork
  6. personA (); // personA tries to get the room and puts it back after work
  7. personB (); // personB also tries to get the room and puts it back after work
  8. #25 personA (); // personA tries to get the room a second time
  9. join_none
  10. end
  11. task getRoom (bit [ 1: 0] id);
  12. $display ( "[%0t] Trying to get a room for id[%0d] ...", $time, id);
  13. key.get ( 1);
  14. $display ( "[%0t] Room Key retrieved for id[%0d]", $time, id);
  15. endtask
  16. task putRoom (bit [ 1: 0] id);
  17. $display ( "[%0t] Leaving room id[%0d] ...", $time, id);
  18. key.put ( 1);
  19. $display ( "[%0t] Room Key put back id[%0d]", $time, id);
  20. endtask
  21. // This person tries to get the room immediately and puts
  22. // it back 20 time units later
  23. task personA ();
  24. getRoom ( 1);
  25. #20 putRoom (1);
  26. endtask
  27. // This person tries to get the room after 5 time units and puts it back after
  28. // 10 time units
  29. task personB ();
  30. #5 getRoom (2);
  31. #10 putRoom (2);
  32. endtask
  33. endmodule

结果:

Mailbox 信箱

信箱是一种允许两个线程之间进行数据交换的通信机制;一个线程要想与另一个线程进行通信,首先要将数据发送到信箱,信箱将数据临时存储在系统定义的内存对象中 ,然后传递给另一个线程.信箱与队列的不同之处在于,队列中的数据可以从头部和尾部存取,而信箱中的数据属于先进先出的类型.

 

一个信箱可以有容量上的限制,也可以没有;当一个发送端线程试图向一个容量固定且已满的信箱中继续存入数据的时候,会发生阻塞直至信箱中有数据被移走;同样的,如果接收端线程试图从一个已空的信箱中获得数据,它也会发生阻塞,直至信箱中有数据存入.

存在两种类型的信箱:

(1)普通信箱--可以存取任何类型的数据,定义格式如下:

 mailbox mailbox_name;
  
  

(2)参数化信箱Parameterized mailbox)-- 存取特定类型的数据

typedef mailbox#(type) mailbox_name;
  
  

信箱是一个内建的类,它提供了一下的几种方法:

new()       

创建信箱对象,语法格式如下:


  
  
  1. mailbox_name = new(); // 创建一个无容量限制的信箱
  2. mailbox_name = new(m_size); //创建一个定容信箱

put()

将数据存入信箱;

try_put()

将数据存入信箱而不发生阻塞;

get() or peek()           

从信箱中获取数据;其中peek()方法用来探视信箱数据而不将数据移出信箱,而get()方法会将数据移出信箱;

try_get( )or try_peek( )

试图获得信箱数据而不发生阻塞,特性与上面一样;

num()

返回信箱中的数据条目;

例子:


  
  
  1. //-------------------------------------------------------------------------
  2. // Packet数据类,产生随机数
  3. //-------------------------------------------------------------------------
  4. class packet;
  5. rand bit [ 7: 0] addr;
  6. rand bit [ 7: 0] data;
  7. //Displaying randomized values
  8. function void post_randomize();
  9. $display( "Packet::Packet Generated");
  10. $display( "Packet::Addr=%0d,Data=%0d",addr,data);
  11. endfunction
  12. endclass
  13. //-------------------------------------------------------------------------
  14. //Generator - Generates the transaction packet and send to driver
  15. //-------------------------------------------------------------------------
  16. class generator; //发送类,将数据类产生的数据存入信箱
  17. packet pkt;
  18. mailbox m_box;
  19. //constructor, getting mailbox handle
  20. function new(mailbox m_box); //发送类与接收类的信箱是一个,所以在构造方法中获得信箱的句柄
  21. this.m_box = m_box;
  22. endfunction
  23. task run; //不能在方法中实例化信箱,这样的话信箱就不止一个了
  24. repeat( 2) begin
  25. pkt = new();
  26. pkt.randomize(); //generating packet
  27. m_box.put(pkt); //putting packet into mailbox 将数据发送到信箱
  28. $display( "Generator::Packet Put into Mailbox");
  29. #5;
  30. end
  31. endtask
  32. endclass
  33. //-------------------------------------------------------------------------
  34. // Driver - Gets the packet from generator and display's the packet items
  35. //-------------------------------------------------------------------------
  36. class driver; //驱动类或接收类,获得信箱中的数据
  37. packet pkt;
  38. mailbox m_box;
  39. //constructor, getting mailbox handle
  40. function new(mailbox m_box); //构造函数获得的信箱句柄与发送类是一个,所以通过构造方法传递信箱的句柄
  41. this.m_box = m_box;
  42. endfunction
  43. task run;
  44. repeat( 2) begin
  45. m_box. get(pkt); //getting packet from mailbox 从信箱中获得数据
  46. $display( "Driver::Packet Recived");
  47. $display( "Driver::Addr=%0d,Data=%0d\n",pkt.addr,pkt.data);
  48. end
  49. endtask
  50. endclass
  51. //-------------------------------------------------------------------------
  52. // tbench_top
  53. //-------------------------------------------------------------------------
  54. module mailbox_ex;
  55. generator gen;
  56. driver dri;
  57. mailbox m_box; //declaring mailbox m_box
  58. initial begin
  59. //Creating the mailbox, Passing the same handle to generator and driver,
  60. //because same mailbox should be shared in-order to communicate.
  61. m_box = new(); //creating mailbox 创建信箱对象,并将信箱句柄赋值
  62. gen = new(m_box); //creating generator and passing mailbox handle 实例化发送类和接收类对象
  63. dri = new(m_box); //creating driver and passing mailbox handle
  64. $display( "------------------------------------------");
  65. fork
  66. gen.run(); //Process-1 创建两个线程,一个发送数据到信箱,一个接收数据到信箱
  67. dri.run(); //Process-2
  68. join
  69. $display( "------------------------------------------");
  70. end
  71. endmodule

结果:

可以看出,实现了两个线程之间的数据交换.


  
  
  1. // Data packet in this environment
  2. class transaction;
  3. rand bit [ 7: 0] data;
  4. function display ();
  5. $display ( "[%0t] Data = 0x%0h", $time, data);
  6. endfunction
  7. endclass
  8. // Generator class - Generate a transaction object and put into mailbox
  9. class generator;
  10. mailbox mbx;
  11. function new (mailbox mbx);
  12. this.mbx = mbx;
  13. endfunction
  14. task genData ();
  15. transaction trns = new ();
  16. trns.randomize ();
  17. trns.display ();
  18. $display ( "[%0t] [Generator] Going to put data packet into mailbox", $time);
  19. mbx.put (trns); //将数据存入信箱
  20. $display ( "[%0t] [Generator] Data put into mailbox", $time);
  21. endtask
  22. endclass
  23. // Driver class - Get the transaction object from Generator
  24. class driver;
  25. mailbox mbx;
  26. function new (mailbox mbx);
  27. this.mbx = mbx;
  28. endfunction
  29. task drvData ();
  30. transaction drvTrns = new ();
  31. $display ( "[%0t] [Driver] Waiting for available data", $time);
  32. mbx. get (drvTrns); //从信箱中取出数据
  33. $display ( "[%0t] [Driver] Data received from Mailbox", $time);
  34. drvTrns.display ();
  35. endtask
  36. endclass
  37. // Top Level environment that will connect Gen and Drv with a mailbox
  38. module tb_top;
  39. mailbox mbx;
  40. generator Gen;
  41. driver Drv;
  42. initial begin
  43. mbx = new ();
  44. Gen = new (mbx);
  45. Drv = new (mbx);
  46. fork
  47. #10 Gen.genData ();
  48. Drv.drvData ();
  49. join_none
  50. end
  51. endmodule

 结果:


  
  
  1. module tb;
  2. // Create a new mailbox that can hold utmost 2 items
  3. mailbox mbx = new( 2);
  4. // Block1: This block keeps putting items into the mailbox
  5. // The rate of items being put into the mailbox is 1 every ns
  6. initial begin
  7. for (int i= 0; i < 5; i++) begin
  8. #1 mbx.put (i);
  9. $display ( "[%0t] Thread0: Put item #%0d, size=%0d", $time, i, mbx.num());
  10. end
  11. end
  12. // Block2: This block keeps getting items from the mailbox
  13. // The rate of items received from the mailbox is 2 every ns
  14. initial begin
  15. forever begin
  16. int idx;
  17. #2 mbx.get (idx);
  18. $display ( "[%0t] Thread1: Got item #%0d, size=%0d", $time, idx, mbx.num());
  19. end
  20. end
  21. endmodule

结果:

  


  
  
  1. // Create alias for parameterized "string" type mailbox
  2. typedef mailbox #( string) s_mbox; //定义一个存储字符串的信箱
  3. // Define a component to send messages
  4. class comp1;
  5. // Create a mailbox handle to put items
  6. s_mbox names;
  7. // Define a task to put items into the mailbox
  8. task send ();
  9. for ( int i = 0; i < 3; i++) begin
  10. string s = $sformatf ( "name_%0d", i);
  11. # 1 $display ( "[%0t] Comp1: Put %s", $time, s);
  12. names.put(s);
  13. end
  14. endtask
  15. endclass
  16. // Define a second component to receive messages
  17. class comp2;
  18. // Create a mailbox handle to receive items
  19. s_mbox list;
  20. // Create a loop that continuously gets an item from
  21. // the mailbox
  22. task receive ();
  23. forever begin
  24. string s;
  25. list.get(s);
  26. $display ( "[%0t] Comp2: Got %s", $time, s);
  27. end
  28. endtask
  29. endclass
  30. // Connect both mailbox handles at a higher level
  31. module tb;
  32. // Declare a global mailbox and create both components
  33. s_mbox m_mbx = new();
  34. comp1 m_comp1 = new();
  35. comp2 m_comp2 = new();
  36. initial begin
  37. // Assign both mailbox handles in components with the
  38. // global mailbox
  39. m_comp1.names = m_mbx;
  40. m_comp2. list = m_mbx;
  41. // Start both components, where comp1 keeps sending
  42. // and comp2 keeps receiving
  43. fork
  44. m_comp1.send();
  45. m_comp2.receive();
  46. join
  47. end
  48. endmodule

结果:

当存数据的信箱和取数据的信箱不匹配时会发生什么情况?我们来看下面的例子:


  
  
  1. // Create alias for parameterized "string" type mailbox
  2. typedef mailbox #( string) s_mbox;
  3. // Define a component to send messages
  4. class comp1;
  5. // Create a mailbox handle to put items
  6. s_mbox names; //信箱为String类型
  7. // Define a task to put items into the mailbox
  8. task send ();
  9. for ( int i = 0; i < 3; i++) begin
  10. string s = $sformatf ( "name_%0d", i);
  11. # 1 $display ( "[%0t] Comp1: Put %s", $time, s);
  12. names.put(s);
  13. end
  14. endtask
  15. endclass
  16. // Define a second component to receive messages
  17. class comp2;
  18. // Create a mailbox handle to receive items
  19. mailbox #(byte) list; //信箱为byte类型
  20. // Create a loop that continuously gets an item from
  21. // the mailbox
  22. task receive ();
  23. forever begin
  24. string s;
  25. list.get(s);
  26. $display ( "[%0t] Comp2: Got %s", $time, s);
  27. end
  28. endtask
  29. endclass
  30. // Connect both mailbox handles at a higher level
  31. module tb;
  32. // Declare a global mailbox and create both components
  33. s_mbox m_mbx = new(); //信箱为String类型
  34. comp1 m_comp1 = new();
  35. comp2 m_comp2 = new();
  36. initial begin
  37. // Assign both mailbox handles in components with the
  38. // global mailbox
  39. m_comp1.names = m_mbx;
  40. m_comp2. list = m_mbx; //将String类型信箱的句柄赋给byte类型
  41. // Start both components, where comp1 keeps sending
  42. // and comp2 keeps receiving
  43. fork
  44. m_comp1.send();
  45. m_comp2.receive();
  46. join
  47. end
  48. endmodule

结果:

由于信箱类型不匹配,导致报错. 

 Event  事件

事件是静态对象,用于线程之间的同步 ;

一个线程可以触发一个事件,另一个线程可以等待另一个事件的触发;事件触发使用"->"或"->>"操作符,等待事件的触发可以使用"@"或"wait()"操作

SV中的事件可以作为同步线程的句柄,可以传递给方法,这个特点使得可以在对象之间共享事件,而不用把事件定义成全局变量;

-> operator

触发一个事件,同时也会触发等待该事件的线程;

 ->> operator 

触发非阻塞事件;

@ operator 

等待一个事件被触发,语法格式如下:

@(event_name.triggered);//等待事件的触发并阻塞线程,边沿敏感
  
  

对于一个触发接触解除一个正在等待事件的线程的阻塞,阻塞的线程必须在时间触发操作->之前执行@语句.如果事件的触发在@语句之前,则线程会继续阻塞.

wait operator 

如果一个事件的触发与等待该事件的@操作符的语句在同一时刻发生,由于@是边沿敏感的,@操作符可能会错过事件的触发,这时候可以用电平敏感的wait()方法来替代@操作符,语法格式如下:

wait(event_name.triggered);
  
  

wait_order() 

 wait_oeder()会阻塞线程的进行,直至括号的的事件按给定的顺序触发,如果括号中的事件没有按给定的顺序触发,也会阻塞线程的进行。

例子:


  
  
  1. wait_order(a,b, c); //阻塞线程,直至事件以a->b->c的顺序依次触发
  2. //如果不是这个触发顺序,则会产生错误
  3. wait_order( a, b, c ) else $display( "Error: events out of order" );
  4. //用一条失败的语句代替了错误的产生,如果不是a->b->c的顺序触发,则会输出"Error: events out of order"
  5. bit success;
  6. wait_order( a, b, c ) success = 1; else success = 0;
  7. //用变量输出代替了上面的输出语句

Merging events  

如果将一个事件赋给另外一个事件,那么触发两个事件中的任意一个,另一个事件都会触发.

看下面例子:


  
  
  1. module events_ex;
  2. event ev_1; //定义 event ev_1
  3. initial begin
  4. fork
  5. //process-1, triggers the event
  6. begin
  7. #40;
  8. $display($time, "\tTriggering The Event");
  9. ->ev_1; //触发事件
  10. end
  11. //process-2, wait for the event to trigger
  12. begin
  13. $display($time, "\tWaiting for the Event to trigger");
  14. @(ev_1.triggered); //等待事件触发
  15. $display($time, "\tEvent triggered");
  16. end
  17. join
  18. end
  19. endmodule

 输出:


  
  
  1. module events_ex;
  2. event ev_1; //declaring event ev_1
  3. initial begin
  4. fork
  5. //process-1, triggers the event
  6. begin
  7. #40;
  8. $display($time, "\tTriggering The Event");
  9. ->ev_1; //事件在40ns被触发
  10. end
  11. //process-2, wait for the event to trigger
  12. begin
  13. $display($time, "\tWaiting for the Event to trigger");
  14. #60;//等待的语句却在60ns
  15. @(ev_1.triggered);
  16. $display($time, "\tEvent triggered");
  17. end
  18. join
  19. end
  20. initial begin
  21. #100;
  22. $display($time, "\tEnding the Simulation"); //事件触发在等待语句之前,所以事件不会触发
  23. $finish;
  24. end
  25. endmodule

结果:

 

 当事件与等待语句在同一时刻进行时,wait()因为是属于电平敏感的语句,所以会检测到事件的触发;而@操作符是边沿敏感的,所以检测不到事件的触发.看下面两个例子:


  
  
  1. module events_ex;
  2. event ev_1; //declaring event ev_1
  3. initial begin
  4. fork
  5. //process-1, triggers the event
  6. begin
  7. $display($time, "\tTriggering The Event");
  8. ->ev_1;
  9. end
  10. //process-2, wait for the event to trigger
  11. begin
  12. $display($time, "\tWaiting for the Event to trigger");
  13. wait(ev_1.triggered);
  14. $display($time, "\tEvent triggered");
  15. end
  16. join
  17. end
  18. endmodule

 可以看到wait()检测到了事件的触发,线程继续往下执行;


  
  
  1. module events_ex;
  2. event ev_1; //declaring event ev_1
  3. initial begin
  4. fork
  5. //process-1, triggers the event
  6. begin
  7. $display($time, "\tTriggering The Event");
  8. ->ev_1;
  9. end
  10. //process-2, wait for the event to trigger
  11. begin
  12. $display($time, "\tWaiting for the Event to trigger");
  13. @(ev_1.triggered);
  14. $display($time, "\tEvent triggered");
  15. end
  16. join
  17. end
  18. endmodule

 而@却错过了事件的触发,导致线程继续阻塞;

下面来看wait_order()的用法:


  
  
  1. module events_ex;
  2. event ev_1; //declaring event ev_1
  3. event ev_2; //declaring event ev_2
  4. event ev_3; //declaring event ev_3
  5. initial begin
  6. fork
  7. //process-1, triggers the event ev_1
  8. begin
  9. #6;
  10. $display($time, "\tTriggering The Event ev_1");
  11. ->ev_1;
  12. end
  13. //process-2, triggers the event ev_2
  14. begin
  15. #2;
  16. $display($time, "\tTriggering The Event ev_2");
  17. ->ev_2;
  18. end
  19. //process-3, triggers the event ev_3
  20. begin
  21. #8;
  22. $display($time, "\tTriggering The Event ev_3");
  23. ->ev_3;
  24. end
  25. //process-4, wait for the events to trigger in order of ev_2,ev_1 and ev_3
  26. begin
  27. $display($time, "\tWaiting for the Event's to trigger");
  28. wait_order(ev_2,ev_1,ev_3) //注意事件触发顺序
  29. $display($time, "\tEvent's triggered Inorder");
  30. else
  31. $display($time, "\tEvent's triggered Out-Of-Order");
  32. end
  33. join
  34. end
  35. endmodule

 事件触发以正常的顺序进行,没有输出错误提示语句;


  
  
  1. module events_ex;
  2. event ev_1; //declaring event ev_1
  3. event ev_2; //declaring event ev_2
  4. event ev_3; //declaring event ev_3
  5. initial begin
  6. fork
  7. //process-1, triggers the event ev_1
  8. begin
  9. #6;
  10. $display($time, "\tTriggering The Event ev_1");
  11. ->ev_1;
  12. end
  13. //process-2, triggers the event ev_2
  14. begin
  15. #2;
  16. $display($time, "\tTriggering The Event ev_2");
  17. ->ev_2;
  18. end
  19. //process-3, triggers the event ev_3
  20. begin
  21. #1;
  22. $display($time, "\tTriggering The Event ev_3");
  23. ->ev_3;
  24. end
  25. //process-4, wait for the events to trigger in order of ev_2,ev_1 and ev_3
  26. begin
  27. $display($time, "\tWaiting for the Event's to trigger");
  28. wait_order(ev_2,ev_1,ev_3)
  29. $display($time, "\tEvent's triggered Inorder");
  30. else
  31. $display($time, "\tEvent's triggered Out-Of-Order");
  32. end
  33. join
  34. end
  35. endmodule

 事件触发为乱序,导致输出了错误提示语句;


  
  
  1. module tb_top;
  2. event eventA; // Declare an event handle called "eventA"
  3. initial begin
  4. fork
  5. waitForTrigger (eventA); // Task waits for eventA to happen
  6. #5 ->eventA; // Triggers eventA
  7. join
  8. end
  9. // The event is passed as an argument to this task. It simply waits for the event
  10. // to be triggered
  11. task waitForTrigger (event eventA);
  12. $display ( "[%0t] Waiting for EventA to be triggered", $time);
  13. wait (eventA.triggered);
  14. $display ( "[%0t] EventA has triggered", $time);
  15. endtask
  16. 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块的语法格式如下:


  
  
  1. program test (input clk, input [ 7: 0] addr, output [ 7: 0] wdata);
  2. ...
  3. endprogram
  4. or
  5. program test ( interface mem_intf);
  6. ...
  7. endprogram

对于program块:

  • 可以实例化,端口可以与module连接;
  • 可以拥有多个initial块;
  • 不能够包含always、module、interface和其他program块
  • 在program块中,变量只能使用阻塞赋值,使用非阻塞赋值会报错; 

 下面的例子将展示用module和program两种不同的块来写testbench之间的不同:


  
  
  1. //-------------------------------------------------------------------------
  2. //design code
  3. //-------------------------------------------------------------------------
  4. module design_ex(output bit [7:0] addr);
  5. initial begin
  6. addr <= 10;
  7. end
  8. endmodule
  9. //-------------------------------------------------------------------------
  10. //testbench
  11. //-------------------------------------------------------------------------
  12. module testbench(input bit [7:0] addr);
  13. initial begin
  14. $display( "\t Addr = %0d",addr);
  15. end
  16. endmodule
  17. //-------------------------------------------------------------------------
  18. //testbench top
  19. //-------------------------------------------------------------------------
  20. module tbench_top;
  21. wire [ 7: 0] addr;
  22. //design instance
  23. design_ex dut(addr); //由于两个module之间同时对一个变量进行操作,产生了竞争
  24. //testbench instance
  25. testbench test(addr);
  26. endmodule

 由于两个module之间的竞争,addr的数据结果为默认值.


  
  
  1. //-------------------------------------------------------------------------
  2. //design code
  3. //-------------------------------------------------------------------------
  4. module design_ex(output bit [7:0] addr);
  5. initial begin
  6. addr <= 10;
  7. end
  8. endmodule
  9. //-------------------------------------------------------------------------
  10. //testbench
  11. //-------------------------------------------------------------------------
  12. program testbench(input bit [7:0] addr);
  13. initial begin
  14. $display( "\t Addr = %0d",addr);
  15. end
  16. endprogram
  17. //-------------------------------------------------------------------------
  18. //testbench top
  19. //-------------------------------------------------------------------------
  20. module tbench_top;
  21. wire [ 7: 0] addr;
  22. //design instance
  23. design_ex dut(addr);
  24. //testbench instance
  25. testbench test(addr);
  26. endmodule

module块在active区域执行,而program块在Reactive执行,也就是说program块在module块之后执行,所以即使对同一变量操作两者之间也不会产生竞争,所以输出正确值。结果如下:

还有一点需要声明的是,program中可以调用module中的方法,但是module中不能调用program中的方法.

Interface 接口 

接口用来连接testbench和design。对比下面两个图:

 

 左边的图为不带接口进行连接的情况,右边的图为两者用接口连接的情况.

接口可以认为是一捆智能的连线,包含了连接、同步、多个块之间的通信功能.

一个接口必须明确:

(1)信号的方向(默认为input),用modport关键字;

(2)时钟信息,采用时钟块clocking block

接口可以包含常数、变量、方法;

下面是一个接口定义的格式:


  
  
  1. interface interface_name;
  2. ...
  3. interface_items //接口中的类型一般定义为logic,它既可以在过程块中赋值,也可以用连续语句赋值
  4. //logic类型只能有单个驱动
  5. ...
  6. endinterface

接口可以像类或module那样实例化,格式:

interface_name inst_name;
  
  

与传统端口连接方式相比,接口的好处有:

(1)将多个信号组合成一捆信号,将多个端口的连接变成一个接口的连接;

(2)接口一旦被定义,就可以通过例化接口来实现模块之间的连接;

(3)添加或删除一个信号很容易;

(4)接口中可以包含task、function、parameter、变量、function coverage、assertion。


  
  
  1. //-------------------------------------------------------------------------
  2. // Verilog Design
  3. //-------------------------------------------------------------------------
  4. module memory ( clk, addr, wr_rd, wdata, rdata);
  5. input clk;
  6. input [ 7: 0] addr;
  7. input wr_rd;
  8. input [ 7: 0] wdata;
  9. output [ 7: 0] rdata;
  10. endmodule
  11. //-------------------------------------------------------------------------
  12. // Interface
  13. //-------------------------------------------------------------------------
  14. interface mem_intf; //定义接口
  15. logic clk;
  16. logic [ 7: 0] addr;
  17. logic wr_rd;
  18. logic [ 7: 0] wdata;
  19. logic [ 7: 0] rdata;
  20. endinterface
  21. //-------------------------------------------------------------------------
  22. // TestCase
  23. //-------------------------------------------------------------------------
  24. program testcase (interface intf); //接口作为参数
  25. environment env;
  26. initial begin
  27. env = new(intf);
  28. end
  29. ……
  30. endprogram
  31. //-------------------------------------------------------------------------
  32. // TestBench Top
  33. //-------------------------------------------------------------------------
  34. module tbench_top;
  35. //Interface instance
  36. mem_intf intf(); //实例化接口
  37. //DUT(Design Under Test) instance
  38. memory dut(
  39. .clk(intf.clk), //引用接口中的变量
  40. .addr(intf.addr),
  41. .wr_rd(intf.wr_rd),
  42. .wdata(intf.wdata),
  43. .rdata(intf.rdata)
  44. );
  45. //TestCase Instance
  46. testcase test(intf);
  47. ……
  48. endmodule

modport 

将接口中的信号端口分组,并明确端口的方向;通过明确了信号的方向,modport提供了访问该信号的限制.例如,modport将信号方向定义为input,则任何企图给该信号赋值的操作都是非法的.modport中可以定义信号的方向包括:

  • inout
  • output
  • input
  • ref

下面举个例子来说明modport的用法:


  
  
  1. interface my_intf;
  2. logic a;
  3. logic b;
  4. //modport delcaration
  5. modport driver (input a, output b); //1个modport中不需要包含所有的信号
  6. modport monitor (input a, input b);
  7. endinterface

一个接口可以包含多个modport关键字,一个信号可以存在多个modport中。如果需要引用上例中的driver,首先需要实例化接口,然后再调用接口中的diver,格式如下:


  
  
  1. my_intf.driver intf1;
  2. //通用格式
  3. interface_name.modport_name 接口实例名;

下面例子展示了如何将接口连接到DUT:


  
  
  1. // Filename : myBus.sv
  2. //定义接口
  3. interface myBus (input clk); //接口可以带参数,参数一般是clk和rst
  4. logic [ 7: 0] data;
  5. logic enable;
  6. // From TestBench perspective, 'data' is input and 'write' is output
  7. modport TB (input data, output enable);
  8. // From DUT perspective, 'data' is output and 'enable' is input
  9. modport DUT (output data, input enable);
  10. endinterface
  11. //定义DUT
  12. // Filename : dut.sv
  13. module dut (myBus.DUT busIf); //注意DUT与接口的连接格式:接口名.modport名 接口实例名
  14. //其实就是创建了一个接口句柄,只不过在接口名后要加modport名
  15. always @ (posedge clk)
  16. if (busIf.enable) //引用接口中的属性与引用对象属性是一样的 接口名.属性名
  17. busIf. data <= busIf. data+ 1;
  18. else
  19. busIf. data <= 0;
  20. endmodule
  21. //在顶层模块中将DUT与接口连接
  22. // Filename : tb_top.sv
  23. module tb_top;
  24. bit clk;
  25. bit enable;
  26. // Create a clock
  27. always # 10 clk = ~clk;
  28. // Create an interface object
  29. myBus busIf (clk); //实例化接口对象
  30. // Instantiate the DUT; pass modport DUT of busIf
  31. dut dut0 (busIf.DUT); //将接口对象作为参数传递给DUT 实例名.modport名
  32. // Testbench code : let's wiggle enable
  33. initial begin
  34. enable <= 0;
  35. # 10 enable <= 1;
  36. # 40 enable <= 0;
  37. # 20 enable <= 1;
  38. end
  39. endmodule

 parameterize an interface 在接口中使用常数(parameter)


  
  
  1. interface myBus #(parameter D_WIDTH= 31) (input clk); //这与常数化module的格式是一样的
  2. logic [D_WIDTH -1: 0] data;
  3. logic enable;
  4. endinterface

Task and Function in interface

接口中可以包含方法,如下:


  
  
  1. interface task_function_if (input clk);
  2. logic req;
  3. logic gnt;
  4. //=================================================
  5. // Clocking block for tb modport
  6. //=================================================
  7. clocking cb @ (posedge clk);
  8. input gnt;
  9. output req;
  10. endclocking
  11. //=================================================
  12. // Task inside a interface, You can basically
  13. // Write a BFM, who's input can be a class or
  14. // a mailbox, so on..... SO COOL!!!!
  15. //=================================================
  16. task drive_req (input integer delay, input bit value);
  17. $display( "@%0dns Before driving req %b from task at %m", $time, req);
  18. repeat (delay) @ (posedge clk);
  19. req = value;
  20. $display( "@%0dns Driving req %b from task at %m", $time, req);
  21. endtask
  22. //=================================================
  23. // Mod port with exporting and importing tasks
  24. //=================================================
  25. modport dut (input clk,req, output gnt
  26. //, export print_md, import print_tb
  27. );
  28. //=================================================
  29. // Mod Port with exporting and importing tasks
  30. //=================================================
  31. modport tb (clocking cb, input clk,
  32. import drive_req //导入方法
  33. //, print_md, export print_tb
  34. );
  35. endinterface

 

  Clocking Block  时钟块

 时钟块明确了一组信号的时序和同步.定义一个时钟块必须明确:

  • 时钟事件用作design和testbench之间的同步参考;
  • 被testbench驱动和采样的一系列信号;
  • 与时钟事件有关的时钟,testbench利用这个时钟来采样和驱动信号
  • 一个接口中可以包括多个时钟块;

时钟块可以定义在接口、module和prgram块中.

下面举例:


  
  
  1. clocking cb @(posedge clk);
  2. default input #1 output #2; //输入输出的时钟偏斜
  3. input from_Dut; //也就是说输入信号的采样在时钟沿的前1ns,输出信号的驱动在时钟沿之后的2ns
  4. output to_Dut;
  5. endclocking

Clocking event:时钟事件  时钟事件用来同步时钟块,如上面的@(posedge clk);

Clocking signal:  时钟信号  信号的采样和驱动在时钟块完成,从DUT到DUT是利用时钟信号完成的;

Clocking skew: 时钟偏斜  上面例子中#1和#2就是时钟偏斜,时钟偏斜明确了输入和输出的时钟信号被采样和驱动的时刻,时钟偏斜可以是常数,也可以是常量表达式;

下面具体讲一下时钟偏斜:

理想情况下信号是在时钟事件(也就是时钟沿)被采样或驱动的,但是如果定义了一个输入偏斜,那么信号的采样就会在边沿之前的偏斜时钟位置采样,对于输出信号就会在边沿之后的时钟偏斜位置被驱动。如果时钟偏斜量没有带单位,则会使用是时钟信号的单位。看下面例子:


  
  
  1. clocking cb @(posedge clk);
  2. default input #1ps output #2;
  3. input from_Dut;
  4. output to_Dut;
  5. endclocking
  6. //也可以将偏斜明确到某个具体的信号
  7. clocking cb @(clk);
  8. input #4ps from_Dut;
  9. output #6 to_Dut;
  10. endclocking

无论用于声明时钟块的实际时钟事件是什么,都可以通过使用时钟块名称直接获得时钟块的时钟事件.也就是说,对于上面例子,@(cb)和@(posedge clk)是等效的.


  
  
  1. clocking ck1 @ (posedge clk);
  2. default input #5ns output #2ns;
  3. input data, valid, ready = top.ele.ready;
  4. output negedge grant; //grant信号有自己的延时要求,只在下降沿被驱动
  5. input #1step addr; //在上升沿到来之前的瞬间addr被采样
  6. input #1 output #3 sig; //sig是一个inout信号,表明在上升沿之前的1ns采样,在上升沿之后的3ns被驱动
  7. endclocking

Cycle delay: ## 周期延时操作符

##操作符可以通过明确延时周期来执行延时操作,例子如下:


  
  
  1. ## 8; // wait 8 clock cycles
  2. ## (a + 1); // wait a+1 clock cycles

  
  
  1. initial begin
  2. // Init the outputs
  3. ram.addr <= 0;
  4. ram.din <= 0;
  5. ram.ce <= 0;
  6. ram.we <= 0;
  7. // Write Operation to Ram
  8. for ( int i = 0; i < 2; i++) begin
  9. // 等效于 repeat (2) @ (posedge clk);
  10. ## 2 ;
  11. ram.addr <= i;
  12. ram.din <= $random;
  13. ram.ce <= 1;
  14. ram.we <= 1;
  15. ## 2;
  16. ram.ce <= 0;
  17. end
  18. // Read Operation to Ram
  19. for ( int i = 0; i < 2; i++) begin
  20. // 等效于 @ (posedge clk);
  21. ## 1 ;
  22. ram.addr <= i;
  23. ram.ce <= 1;
  24. ram.we <= 0;
  25. // Below line is same as repeat (3) @ (posedge clk);
  26. ## 3;
  27. ram.ce <= 0;
  28. end
  29. #40 $finish;
  30. end

 

 下面对上面所讲举一个完整的例子:


  
  
  1. interface arb_if(input clk);
  2. logic [ 1: 0] grant,request;
  3. logic rst;
  4. //定义时钟块
  5. clocking cb @(posedge clk)
  6. output request;
  7. input grant;
  8. endclocking
  9. //定义modport
  10. modport TEST(clocking cb,output rst); //使用cb
  11. modport DUT(input request,input rst,output grant);
  12. endinterface
  13. //简单的使用接口的测试平台
  14. module test(arb_if.TEST arbif);
  15. initial begin
  16. arbif.cb.request<= 0;
  17. @arbif.cb;
  18. $display( "grant=%b",arbif.cb.grant);
  19. end
  20. endmodule

 Virtual Interface 虚接口

一个虚接口就是一个变量,该变量代表了接口的一个实例,用关键字virtual来定义,定义格式如下:

virtual interface_name instance_name;

  
  
  • 虚接口在使用前必须初始化,并且虚接口必须连接或指向一个实际的接口(driver中的虚接口)
  • 访问为初始化的虚接口会报错;
  • 虚接口可以定义为类的属性,因此可以在过程语句或构造函数new()中初始化;
  • 虚接口变量可以作为参数被传递给方法;
  • 所有的接口方法都可以通过虚接口句柄来访问; 

虚接口的出现是为了在driver和monitor中获得接口的实例,但是接口不能在类中实例化(只能在module和program中),这是因为接口都是static的,而类是dynamic的,所以类中不允许存在接口只能存在虚接口!由于虚接口实际上是接口的实例,所以它是以成员的形式存在于类中的. 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值