システムVerilog

SVデータ型

SV には 6 つの組み込みデータ型があります。

  1. 論理
  2. 少し
  3. バイト
  4. ショートイント
  5. 整数
  6. 倍長整数

注: SV は Verilog データ型とも互換性があります。つまり、Verilog データ型 SV も含まれます。たとえば、Verilog ネットタイプ、reg、integer、変数の実数などのワイヤです。

論理:

SV のロジックは 4 値ロジックです。つまり、その値は 0/1/x/z の 4 つの値を持つことができます。

ロジックはプロセスごとまたは継続的に割り当てることができ、コンパイラはロジックが reg か Wire かを自動的に推測できます。

唯一の制限は、ロジックでは 1 つの入力のみが許可され、複数の構造ドライバーを持つことができないことです。たとえば、inout タイプのポートをロジックとして定義することはできません。したがって、単一のドライブにはロジックが使用され、複数のドライブにはワイヤが使用されます。

少し:

  1. SV のビット タイプは 2 状態、2 値論理、それぞれ 0/1 です。

  2. ビットタイプは符号なしの数値です

**バイト/短整数/整数/倍長整数: **

  1. バイト/短整数/整数/倍長整数:
  2. データ型はすべて 2 状態、2 値ロジック、それぞれ 0/1 です。
  3. ビット幅はそれぞれ8/16/32/64です
  4. 符号付きの数字です
データの種類 2値論理/4値論理 デフォルト 署名された番号はありますか
論理 4 バツ 署名されていない
少し 2 0 署名されていない
バイト 2 0 署名済み
ショートイント 2 0 署名済み
整数 2 0 署名済み
倍長整数 2 0 署名済み

SV固定幅配列

1. 固定幅配列形式の宣言

固定幅配列の形式宣言は次のとおりです。型名 [定数]。ここで、型は格納されたデータの型です。

例えば:

int array[16]、array は配列の名前、int は配列の記憶域タイプ、data は int データ、16 は配列のサイズ、合計 16 要素です。

int array[8] [4]、array は配列の名前、int は配列の格納タイプ、data は int データ、配列のサイズは 8 行 4 列、合計 32 要素です。

2. 配列の基本操作
配列の初期化

代入表記法 `{} および `{n{}} を使用します。

int mouer[4] = ‘{0,1,2,3};       // 对4个元素进行初始化
int mouer[5];
mouer = ‘{4,3,2,1,0};            // 为5个元素赋值
mouer[0:2] = ‘{5,6,7};          // 为前3个元素赋值 
mouer = ‘{4{8}};                 // 4个值全部为8 {8,8,8,8}
mouer= ‘{9, 8, default:-1};   // {9,8, -1, -1, -1}
トラバース用

次の例に示すように、配列を走査する一般的な方法の 1 つは for ループです。

initial begin
  bit [31:0] src[5],dst[5];
  for (int i=0; i<$size(src); i++)
    src[i] = i;
end

注記:

  1. i 変数は、for ループ内のローカル変数です。

  2. SV では、$size 関数は配列の幅を返します。

  3. $size 関数を使用する代わりに、配列の最大次元 (上記の例では 4) を直接使用できます (インデックスは 0 ~ 4)。

トラバースフォーリーチ

二次元配列も SV でよく使われる配列で、配列に格納されるデータが 2 次元であることから二次元と理解できます。

foreach を使用すると、配列を簡単かつ迅速に走査することもできます。配列名を指定し、次の角かっこ内にインデックス変数を指定するだけです。SV は自動的に配列を走査し、インデックス変数は自動的に宣言され、インデックス変数はループ。次の例に示すように:

initial begin
  bit [31:0] src[5],dst[5];
  foreach (dst[j])
    dst[j] = src[j]* 2;   // dst doubles src values
end
3. 配列に関する注意事項
  1. 配列は 1 次元または多次元にすることができます

  2. byte/short/int型のデータは1ワード(32bit)の記憶空間を占有します。

  3. longint は 2 ワード (64 ビット) の記憶領域を占有します

  4. 配列が範囲外で読み取られた場合は、配列要素の型のデフォルト値が返されます。

    4.1 配列要素がロジックなどの 4 値論理の場合は x を返します。

    4.2 配列要素が int や bit などの 2 値論理の場合は 0 を返す

    4.3 このルールは、固定幅配列、動的配列、連想配列、キュー、およびアドレスに x または z が含まれる場合に適用されます。

  5. 配列が範囲外に書き込まれる場合、書き込まれたデータは無視されます。

SVダイナミックアレイ

1. 動的配列

SV は、シミュレーション中にスペースを割り当てたり、幅を調整したりできる動的配列を提供します。

動的配列の宣言では空の [] が使用され、配列の幅はコンパイル時には指定されず、プログラムの実行時に指定されます。

動的配列は最初は空であるため、領域を割り当てるには new[] 演算子を呼び出し、角かっこ内に配列の幅を渡す必要があります。

動的配列の宣言形式は、type name []です。ここで、 type は格納されたデータの型です。[] は宣言時には空です。

: int array[]、ここで、array は配列の名前、int は配列データの int データとしての格納タイプです。配列を使用する前に、**new[]** 関数を呼び出して、配列にスペースを割り当てる必要があります。

int x[];
x=new [5];
2. 動的配列のいくつかのメソッド

deletesize

int test[];
test=new[4];  //用new来创建数组
num=test.size();  //用size获取数组大小
test.delete();  //用delete删除数组

注: $size(test) は配列のサイズを取得することもできます

SV連想配列

1. 連想配列

SV は、スパース データを格納するための連想配列タイプを提供します。他の配列とは異なり、連想配列は断片化された記憶領域の使用に適しており、次の図に示すように、連想配列に格納されるデータは連続した記憶領域ではありません。

データ
索引 0 3 50 100

連想配列の宣言方法:データ型配列名[インデックス型]、

たとえば、int array[string]の場合、 int は連想配列、格納されるデータのタイプは int タイプのデータ、配列のインデックスは文字列インデックスです。

2. 連想配列のインデックス付け

連想配列のインデックスは次のいずれかになります。

  1. ワイルドカード インデックス: 任意のデータ型のインデックス: int array_name [*];

  2. 文字列インデックス: int array_name[string];

  3. クラスインデックス: int array_name[some_Class];

  4. 整数 (または int) インデックス: int array_name[integer];

  5. 符号付きパック配列のインデックス付け: typedef bit signed [4:1] Nibble; int array_name[Nibble];

  6. 符号なしパック配列のインデックス付け: typedef bit [4:1] Nibble; int array_name [Nibble];

  7. その他のユーザー定義型インデックス: typedef struct { real R; int I[*]; } Unpkt; int array_name [Unpkt];

実際、連想配列は、宣言された型の要素のルックアップ テーブルを実装します。インデックスとして使用されるデータ型は、ルックアップ テーブルのルックアップ キーとして機能し、順序を強制します。例えば:

integer i_array[*];   // 整数关联数组(未指定索引)
bit [20:0] array_b[string];  // 21位向量的关联数组,使用字符串类型作为索引
event ev_array[myClass];   // 事件类型的关联数组,使用类myClass索引
3. 連想配列のいくつかの使用法
num()  //返回数组长度
delete()  //删除指定元素或者所有元素
exists()  //检查是否元素存在,存在返回1,否则返回0
first()  //将指定的索引变量赋值为数组第一个索引的值
last()  //将指定的索引变量赋值为数组最后一个索引的值  
next()  //索引变量被赋值为下一个条目的索引
prev()  //索引变量被赋值为上一个条目的索引
4. 連想配列に関する考慮事項

単純な for ループでは連想配列を走査することはできません。配列を走査するには foreach を使用する必要があります。また、組み込みの first() 関数と next() 関数を使用することもできます。

存在しない連想配列要素を読み取り、4 値論理の場合は x を返し、2 値論理の場合は 0 を返します。

連想配列のインデックスには、文字列、整数、さらにはクラスやユーザー定義のデータ型など、さまざまなデータ型を使用できます。

initial begin
  bit [63:0] assoc[int],idx =1;
  // Initialize widely scatteredvalues
  repeat (64) begin
    assoc[idx] =idx;
    idx = idx << 1;
  end
  // Step through all index values withforeach
  foreach (assoc[i])
  $display("assoc[%h]= %h", i, assoc[i]);
  // Step through all index values withfunctions
  if (assoc.first(idx))
    begin // Get first index
      do
        $display("assoc[%h]=%h",idx, assoc[idx]);
      while (assoc.next(idx)); // Get next index
    end
// Find and delete the first element
  assoc.first(idx);
  assoc.delete(idx);
  $display("The array now has %0delements", assoc.num);
end

SVアレイ法

SV は、固定幅配列、動的配列、キュー、連想配列など、あらゆる非結合配列タイプに対して多数の配列メソッドを提供します。これらの方法は複雑なものから単純なものまであり、合計、積、並べ替えなど多岐にわたります。

1. 配列縮小法

配列のリダクション方法: sum (合計)、product (積)、および (and)、or (or)、xor (排他的論理和)


byte b[] = ‘{ 1, 2, 3, 4};
int y;
y = b.sum ; // y becomes 10 => 1 + 2 +3 + 4
y = b.product ; // y becomes 24 => 1 * 2 *3 * 4
y = b.xor with ( item + 4 ); // y becomes12 => 5 ^ 6 ^ 7 ^ 8

logic [7:0] m [2][2] = '{ '{5, 10}, '{15, 20} };
int y;
y = m.sumwith (item.sumwith (item)); // y becomes 50 => 5+10+15+20

logic bit_arr[1024];
int y;
y = bit_arr.sumwith ( int'(item) ); // forces result to be 32-bit
1. アレイの配置方法
int f[6] =‘{1,6,2,6,8,6};
int d[] = ‘{2,4,6,8,10};
int q[$] = {1,3,5,7}, tq[$];
tq = q.min();// {1}
tq = d.max();// {10}
tq = f.unique();// {1,6,2,8}

注: tq はキューです。つまり、この例の配列メソッドはキューを返します。

1. 配列のソート方法
int d[] = ‘{9,1,8,3,4,4};
d.reverse();// ‘{4,4,3,8,1,9}
d.sort();// ‘{1,3,4,4,8,9}
d.rsort();// ‘{9,8,4,4,3,1}
d.shuffle();// ‘{9,4,3,8,1,4}

SV の構造と列挙

1. 構造
struct {
    int   a;
    int   b;
    bit   c;  
} struct_name

構造体内の変数の参照方法は「.」、つまりstruct_name.aで構造体内の変数を参照できます。

割り当てを定義する
typedef struct {
  int addr = 1 ;
  int crc;
  byte data [4] = '{4{1}};
} packet1;
個人の課題
packet1pi = '{1,2,'{2,3,4,5}};
参照メンバー変数の割り当て
typedef struct {
  int addr = 1 + constant;
  int crc;
  byte data [4] = '{4{1}};
} packet1;
Packet1  p1;
p1.addr = 32’h0000_0080;
p1.data = 32’h8c;
struct{ bit [7:0] opcode; bit[23:0]addr; }IR; 
                                                
IR.opcode = 1; // set field in IR.

typedef struct {
  bit [7:0] opcode;
  bit [23:0] addr;
} inst; // named inst type
inst IR; // define variable
2. 列挙

SV 列挙は、ユーザーが命令内のオペコードやステート マシン内のコードなどの特定の名前のコレクションを読みやすくカスタマイズできるようにする強力な変数タイプを提供します。このデータ型は列挙型と呼ばれます。

列挙型の宣言形式は次のとおりです: enum {変数名, ..., 変数名} enum_name

例:列挙型 {RED,BLUE,GREEN} カラー

SVキュー

1. 行列

SV では、キューのデータ型が導入されています。これは使いやすく、動的配列よりもパフォーマンスがはるかに優れています。

キューには、SV 組み込みデータ型やユーザー定義データ型を含む、あらゆるデータ型を保存できます。キューはテーブルを維持することに相当し、テーブルを任意に追加、削除、変更、検索できます。

キューの順序はユーザーによって維持されます。

キューの宣言形式はdata_type キュー名[$]です。

例: **int data_q [ ] ∗ ∗ (ここで、 int はキューに格納されているデータ型です。 int 型データです)。キューを宣言するときは、記号 [ ]** を使用します。ここで、 int はキューに格納されているデータ型です。 int型データ、宣言キューイング時に記号[を使用int はキューに格納されるデータ型です。キューを宣言するときに記号[ ]が使用されます。

2. キュー方式
queue_name.size           //返回queue的大小
queue_name.insert(index,item)    //在某个索引处插入某元素
queue_name.delete(index)           //刪掉某元素或整个queue
queue_name.pop_front()         //去除第一个元素
queue_name.pop_back()         //去除最后一个元素
queue_name.push_front()   //插入元素到queue(0)
queue_name.push_back()         //插入元素到queue($)
3. キューに関する考慮事項
  1. キューの記憶領域は無限であり、理論的には 0 から $ までの物理メモリの最大領域です。

  2. 要素はキュー内のどこでも追加、削除、変更、確認できます。

  3. キューにコンストラクター new[] を使用しないように注意してください。

int q[$] = {2, 4, 8};    
int p[$];    
int e, pos;
e = q[0];   // 读取第一个(最左边)条目。
e = q[$];   // 读取最后一个(最右边)条目。
q[0] = e;   // 写第一个元素
p = q;   // 读和写整个队列(拷贝)
q = {q, 6};     //在队列的尾部插入'6'

q = {e, q};          // 在队列的头部插入'e'
q = q[1:$];          // 删除第一个(最左边)元素
q = q[0:$-1];          // 删除最后一个(最右边)元素
q = q[1:$-1];          // 删除第一个和最后一个元素
q = {};          // 清除队列(删除所有的元素)
q = {q[0:pos-1], e, q[pos:$]};    // 在位置'pos'处插入'e'
q = {q[0:pos], e, q[pos+1:$]};    // 在位置'pos'之后插入'e’

SV 手続きステートメント

1. 最初のステートメント

概要にない Verilog と同じ

2. always ステートメント

SV は、always に加えて、Verilog の always ステートメントを拡張しました。

  1. always_comb //組み合わせロジックモデリング

  2. always_latch // ラッチをモデル化する

  3. always_ff //シーケンシャルロジックのモデリング

このうち、always_comb は組み合わせロジックをモデル化するために SV で追加されたステートメントであり、always_latch はラッチをモデル化し、always_ff は順序ロジックをモデル化します。

always @(posedge clk or negedge rst_n) begin
  if(~rst_n)begin
  ...
  end else begin
  ...
  end
end
always_comb
   a = b & c;
always_ff @(posedge clockor posedge reset) begin
   r1 <= reset ? 0 : r2 + 1;
   ...
end
always_latch
   if(ck) q <= d;
3. 最終声明
  1. 最終ブロックは最初のブロックと似ており、両方とも手続き型ステートメント ブロックを定義します。違いは、最終ブロックはシミュレーションの終了前にのみ実行されることです。通常、最終ブロックはシミュレーションに関する統計を表示するために使用されます。

  2. 最終ブロックで使用できるステートメントは、関数宣言で使用できるステートメントと同じです。初期ブロックとは異なり、最終ブロックは別のプロセスとして実行されません。

  3. $finish への明示的または暗黙的な呼び出しによってシミュレーションが終了すると、最後のブロックが実行されます。

  4. 最終ブロックはシミュレーション内で 1 回だけトリガーできます。

final
   begin
        $display("Number of cyclesexecuted %d",$time/period);
        $display("Final PC = %h",PC);
   end
initial begin
   …
   $finish();
end

SV制御フロー

1. ステートメントを選択します
if-else ステートメント

ユニークな場合

  1. unique if は、一連の if...else...if 条件に重複があってはいけないこと、つまり条件が相互に排他的であることを示します。

  2. 複数の条件が true の場合、エラーがスローされます。有効なオプションはすべて指定されており、省略されるものはありません。

  3. 「true」の条件がない場合、または「true」の条件がなく、最後の if に対応する else ステートメントがない場合も、エラーが報告されます。

unique if ((a==0) || (a==1)) $display("0 or 1");
else if (a == 2)$display("2");
else if (a == 4)$display(“4”);  // 值3,5,6,7会引起一个错误

場合の優先順位

  1. priority if は、一連の if...else...if 条件がリストされた順序で評価される必要があることを示します。

  2. 複数の条件が同時に満たされる場合は、条件を満たす最初のブランチ (優先ロジックを持つ) が選択されます。

  3. ソフトウェア ツールが「true」の条件がないこと、または「true」の条件がないことが検出され、最後の if に対応する else ステートメントがない場合、エラーが報告されます。

priority if (a[2:1]==0) $display("0 or 1");
else if (a[2]==0) $display("2 or 3");
else $display("4 or 7");// 覆盖了所有可能的其他值,因此没有错误。
ケースステートメント

優先案件

bit [2:0] a;
priority case(a) // 值4,5,6,7会引起一个运行时警告
   3'b00?: $display("0 or 1");
   3'b0??: $display("2 or 3");
endcase

ユニークなケース

unique case应该检查case条目的交迭。如果多于一个case条目匹配于case表达式,那么unique case应该发布一条警告信息。

bit [2:0] a;
unique case(a)  // 值3,5,6,7会引起一个运行时警告
   0,1: $display("0 or 1");
   2: $display("2");
   4: $display("4");
endcase
2. 循环语句
  1. Verilog提供了for、while、repeat以及forever循环。

  2. SystemVerilog增强了Verilog的for循环。

  3. SystemVerilog加入了一个do…while循环和一个foreach循环。

//for
for (int i = 0; i <= 255; i++)
    
//do while
do                             // do...whileloop
  sum += array[j];             // Accumulate
while(j--);  

//foreach
foreach (prod[k,m])
   prod[k][m] = k * m;    // 初始化

//repeat
repeat(5) @(posedge clk)
3. 跳转语句

SystemVerilog加入了像C语言中一样的跳转语句:break、continue和return。

break

像C语言一样跳出循环,结束循环体。

continue

像C语言一样跳转到循环的尾部,结束本次循环,继续下一次循环。

return expression

退出一个函数且具有返回值,expression必须具有正确的类型,否则会报错。

return

退出一个任务或void函数

4. disable语句

disable语句用于终止正在运行的代码块。可以分为diable fork以及disable lable

SystemVerilog可以在任务中使用return,但也支持disable。

如果disable被应用到一个命名的任务,那么这个任务中所有当前正在运行的部分都会被关闭。

for (int i = 0; i < 5; i++)begin : forloop
   if( i == 3 )
      disable forloop
   $display("i=%0d",i);
end
5. event语句
  1. event是一个静态的句柄,用于同步多个线程,在一个线程中触发事件,在另外一个线程中等待事件。

  2. 事件的句柄可以是null。

  3. 声明一个事件如下方式:event done;

  4. 事件的触发使用**->,事件的等待使用@或者.triggered**

module tb;
	event done;
	initial begin
		#20 ->done;
        $display("[%0t] Thread1: triggered,",$time);
	end
	initial begin
        $display("[%0t] Thread2: waiting,",$time);
        @(done);
        $display("[%0t] Thread2: received,",$time);
    end
    initial begin
        $display("[%0t] Thread3: waiting,",$time);
        @(done.triggered);
        $display("[%0t] Thread3: received,",$time);
    end

SV块语句

SV task与function

1. task

SV对task进行了增强和扩展,扩展内容如下:

  1. SystemVerilog加入了在静态任务和函数中声明自动变量的功能。
  2. SystemVerilog加入了在自动任务和函数中声明静态变量的功能。
  3. 无需一个begin…end块或fork…jion块就可以在一个任务或函数中使用多条语句的能力。
  4. 在到达任务或函数的结尾之前从任务或函数返回的能力。
task的参数
input     // 在开始的时候拷贝值
output   // 在结束的时候拷贝值
inout     // 在开始的时候拷贝,在结束的时候输出
ref        // 传递引用

其中,input为输入参数的传递,为task开始调用时,将实参的数值copy一份传递给task的形参。类似的,output与inout同样是参数的copy传递给形参。ref为实参引用的传递,传递的是实参的地址,值得注意的是,如果在task内部将参数修改,那么实参的数值对应的也会被修改。

在SystemVerilog中,如果没有指定参数的方向,那么它的缺省方向是输入。一旦指定了一个方向,那么它就成为后续参数的缺省方向。在下面的例子中,形式参数a和b缺省为输入,u和v都是输出。

task mytask(a, b, output logic [15:0] u, v);
   ...
endtask

每一个形式参数都具有一个数据类型,它或者显式声明,或者从一个缺省类型继承。SystemVerilog中任务参数的缺省类型为logic。

SystemVerilog允许将一个数组声明为task的形式参数。例如:

task mytask(input [3:0][7:0] b[3:0], output[3:0][7:0]y[1:0]);
   ...
endtask
task的注意事项
  1. 在SystemVerilog中,多条语句可以在task和endtask之间被写入,可以省略begin…end。如果省略begin…end,这些语句也是顺序地执行,这与它们被包含在begin…end中是一样的。

  2. task中没有任何语句也是合法的,即空task。

  3. 在Verilog中,当task到达endtask的时候任务退出。而对于SystemVerilog,在endtask关键字之前可以使用return语句退出task。

2. function
function的参数

SystemVerilog扩展了Verilog函数的能力,它允许函数具有与任务相同的形式参数。参数用法与task一致

input     // 在开始的时候拷贝值
output   // 在结束的时候拷贝值
inout     // 在开始的时候拷贝,在结束的时候输出
ref        // 传递引用
function返回值

在Verilog中,函数必须具有返回值。返回值是通过为函数的名字赋值来完成的。

SystemVerilog允许将函数声明成void类型,它没有返回值。对于非void函数,可以使用return语句实现。例如:

function int myfunc (input int x,y);
   return x * y - 1;    //使用return语句指定返回值
endfunction

SV 丢弃函数返回值:通过将函数返回值强制转换成void类型,SystemVerilog允许使用void数据类型来忽略一个函数的返回值。调用方法如下:

void'(some_function());
3. task与function参数传递

V中task与function的参数传递,共有四种方式,分别为:通过值传递,通过引用传递,缺省的参数值以及通过名字传递。

通过值传递
  1. 通过值传递是向function传递参数的缺省机制,它也是Verilog-2001提供的唯一的参数传递机制。这种参数传递机制是通过将每一个参数拷贝到function区域的方式实现的。

  2. 如果function是automatic的,那么function在它的堆栈中保留一个参数的本地拷贝。如果参数在function中被改变,那么这种改变在function外是不可见的。

  3. 当参数很大的时候,我们可能不希望拷贝这个参数。拷贝会造成性能上的影响。

通过值传递
  1. 通过值传递是向function传递参数的缺省机制,它也是Verilog-2001提供的唯一的参数传递机制。这种参数传递机制是通过将每一个参数拷贝到function区域的方式实现的。

  2. 如果function是automatic的,那么function在它的堆栈中保留一个参数的本地拷贝。如果参数在function中被改变,那么这种改变在function外是不可见的。

  3. 当参数很大的时候,我们可能不希望拷贝这个参数。拷贝会造成性能上的影响。

通过引用传递

通过引用传递的参数不会拷贝到function区域,相反,一个对原始参数的引用会被传递到function。然后function可以通过引用访问参数数据。为了指示通过引用传递的参数,参数声明需要以ref关键字开始。基本语法如下:

subroutine(ref type argument);
function int crc(ref byte packet[1000:1]);
   for(int j=1; j<=1000; j++) begin
        crc ^= packet[j];
   end
endfunction
byte packet1[1000:1];
int k = crc(packet1);  // 无论是通过值传递还是通过引用传递,调用方法是一样的

当通过引用传递参数的时候,在调用者内或function中对参数所作的任何改变对两者都是可见的。在function的外部可以立即(在function返回之前)看到变量的变化。

为了保护通过引用传递的参数不被子例程修改,可以将const限定符与ref一起使用,用来表明尽管这个参数是通过引用传递,但它是一个只读变量。例如:

task show (constref byte [] data);
   for (int j = 0; j < data.size; j++)
        $display(data[j]); // 数据可以被读出但不能被修改
endtask
缺省参数值

为了处理一些共用的情况或者考虑一些不用的参数,SystemVerilog允许子例程声明为每一个单一参数指定一个缺省值。 缺省参数的语法如下:

subroutine([direction] [type]argument = default_value);
//可选的direction可以是input、inout、或ref(不能指定输出端口的缺省值)。

例如:这个例子声明了一个任务,read(),它具有两个缺省参数,j和data。接着这个任务就可以使用不同的缺省参数调用:

task read(int j=0, int k, intdata=1);
   ...
endtask;

read( , 5);        // 等价于read(0, 5, 1);
read(2, 5);       // 等价于read(2, 5, 1);
read( , 5,  );     // 等价于read(0, 5, 1);
read( , 5, 7);    // 等价于read(0, 5, 7);
read(1, 5, 2);   // 等价于read(1, 5, 2);
read();             // 错误;k没有缺省值
通过名字传递参数
function int fun(int j=1, strings="no");
   ...
endfunction
fun(.j(2), .s("yes")); //fun(2, "yes");
fun(.s("yes"));        // fun(1, "yes");
fun(, "yes");          // fun(1, "yes");
fun(.j(2));            // fun(2, "no");
fun(.s("yes"), .j(2)); //fun(2 , "yes");
fun(.s(), .j());       // fun(1 , "no");
fun(2);                // fun(2, "no");
fun();                 // fun(1, "no");

SV进程之间的通信

Verilog提供了基本的同步机制(也就是->和@),它们仅限于静态对象,然而它们却不能满足高度动态、灵活的测试平台的需要。

SystemVerilog加入了强大而又易于使用的同步和通信机制的集合,所有这些都可以动态地产生和回收。

SystemVerilog加入了一个内建的semaphore类,这个内建类可以用来同步以及相互排斥地访问共享资源。一个mailbox内建类可以用作是进程间的通信通道。SystemVerilog还增强了Verilog的命名事件数据类型以便满足许多系统级的同步需求。

1. event
event  status;  //定义了一个事件,事件的名称为 status

SV中触发一个事件时,被命名事件可以通过-> 操作符触发。触发一个事件可以为当前等待这个事件的所有进程解除阻塞。

等待一个事件被触发的基本机制是通过事件控制操作符 @。@操作符阻塞调用进程直到指定的事件被触发。

等待进程在触发进程执行触发操作符->之前必须执行@语句。如果触发器首先执行,那么等待进程会保持在阻塞状态。

2. semaphore

semaphore是SV中一个内建的类,它提供了下列方法:

  • 产生一个具有指定数目键值的semaphore:new() 方法。new()函数返回semaphore的句柄,如果没有产生semaphore则返回null。
  • 从桶中获取一个或多个键值:get()方法。get()指定了需要从semaphore中获得的键值的数目,它的缺省值为1。如果指定的键值数目有效,那么方法返回并且进程会继续执行;如果指定的键值数目无效,进程会阻塞直到键值变成有效。
  • 向桶中返回一个或多个键值:put()方法。当调用semaphore.put()任务的时候,指定数目的键值被返回到semaphore中。
  • 尝试无阻塞地获取一个或多个键值:try_get()方法。如果指定的键值数目有效,那么try_get()方法返回1并且进程会继续执行;如果指定的键值数目无效,那么try_get()方法返回0。
3. mailbox

maibox是一种通信机制,它使得消息能够在进程间通信。一个进程发送到mailbox的数据可以被另外一个进程获得。

mailbox的行为就像一个真实的邮箱一样。SystemVerilog的mailbox以一个可控的方式来传输和接收数据。在产生mailbox的时候,它可以具有固定大小也可以无限大。当一个具有固定大小n的mailbox包含了 n个消息的时候,mailbox会变满。

声明一个mailbox的方法如下:mailbox boxname;

mailbox同样是SV一个内建的类,它提供了下列方法:

  • 产生一个mailbox:new()
  • 将一个消息放置到一个mailbox中:put()
  • 尝试将一个消息无阻塞地放置到一个mailbox中:try_put()
  • 从一个mailbox中重新获得一个消息:get()或者peek()
  • 尝试无阻塞地从一个mailbox中重新获得一个消息:try_get()或try_peek()
  • 获得mailbox中消息的数目:num()

如果mailbox已满,则put()会阻塞,如果mailbox为空,则get()会阻塞。peek()任务可以获取对mailbox里面数据的拷贝而不会移除原始数据

new()方法
  • mailbox new()方法的原型为 function new(int bound = 0);
  • new()函数返回mailbox的句柄,如果不能产生mailbox则返回null。
  • 如果bound参数为0,那么mailbox是无边界的(缺省情况),此时一个put()操作应该永远不会阻塞;
  • 如果bound为非0,那么它表示mailbox队列的尺寸。
  • bound必须是正的。负的边界是非法的并会导致不确定的行为。
put()方法
  • put()方法将一个消息放入到一个mailbox中。
  • put()方法严格按FIFO的顺序将一个消息存储在mailbox中。如果mailbox使用一个有界的队列产生,那么put()进程应该被挂起直到队列中有足够的空间。
try_put()方法
  • try_put()方法尝试将一个消息放置在一个mailbox中。try_put()方法严格按FIFO的顺序将一个消息存储在mailbox中。这个方法仅仅对有界的mailbox才有意义(因为对于无界的mailbox来说,put()永远不会阻塞,即put()永远能够成功)。
  • 如果mailbox没有满,那么指定的消息被放置在mailbox当中并且函数返回1。如果mailbox满了,那么函数返回0。
get()方法
  • get()方法从一个mailbox中获得一个消息。
  • get()方法从mailbox中获得一个消息,mailbox则在队列中删除这个消息。
  • 如果mailbox是空的,那么当前的get()进程阻塞直到一个消息被放置到mailbox中。
  • 如果在消息变量和mailbox中的消息间存在类型不匹配,那么会产生一个运行时错误。
try_get()方法
  • try_get()方法尝试无阻塞地从一个mailbox中获得一个消息。
  • 如果mailbox是空的,那么try_get()方法返回0。
  • 如果在消息变量和mailbox中的消息间存在类型不匹配,那么try_get()方法返回-1。
  • 如果一个消息是有效的,并且消息类型与消息变量的类型匹配,那么消息被重新获得并且try_get()方法返回1。
peek()方法
  • peek()方法从一个mailbox中拷贝一个消息但不会将其从队列中删除。
  • 如果mailbox是空的,那么当前的进程会阻塞直到一个消息被放置到mailbox中。
  • 如果在消息变量和mailbox中的消息间存在类型不匹配,那么会产生一个运行时错误。
try_peek()方法
  • try_peek()方法尝试从一个mailbox中拷贝一个消息但不会将其从队列中删除。
  • 如果mailbox是空的,那么try_peek()方法返回0。
  • 如果在消息变量和mailbox中的消息间存在类型不匹配,那么try_peek()方法返回-1。
  • 如果一个消息是有效的,并且消息类型与消息变量的类型匹配,那么消息被拷贝并且try_peek()方法返回1
num()方法

一个mailbox中消息的数目可以通过num()方法获得

参数化的mailbox

缺省的mailbox是无类型的,一个mailbox可以发送和接收任何类型的数据。这是一个非常强大的机制。但是也会因为一个消息与get()到消息变量间的类型不匹配而导致运行时错误。

一个mailbox经常被用来传输一个特定的消息类型,在这种情况下,如果能够在编译时发现类型不匹配,那会是很有用的。 此时需要一个参数化的mailbox。

参数化的mailbox声明如下:mailbox#(type=dynamic_type),其中dynamic_type代表一个特殊的类型,它能够执行运行时的类型检查。

参数化的mailbox提供了所有与无类型的mailbox相同的标准方法:num()、new()、get()、peek()、put()、try_get()、try_peek()、try_put()。

无类型的mailbox与参数化mailbox之间唯一的不同是:对于一个参数化的mailbox,编译器能够确保put()、try_put()、peek()、try_peek()、get()和try_get()与mailbox消息类型兼容,从而所有的类型不匹配都可以被编译器在编译期间捕获而不是在运行时捕获。

参考链接-IC学社

おすすめ

転載: blog.csdn.net/Mouer__/article/details/126021572