第二章:資料型別(續)

weixin_34208283發表於2018-05-10

2.6 連結串列

SystemVerilog提供了連結串列資料結構,但是應該避免使用它,因為SystemVerilog提供的佇列更加高效易用。

2.7陣列的方法

SystemVerilog提供了很多種陣列的表示方法,那麼我們對這些陣列的操作方法又有哪些呢?下面就來一一介紹一下。

2.7.1 陣列的縮減方法

基本的陣列縮減方法就是把一個陣列縮減成一個值。最常用的方法就是求和sum,除此之外還有product(乘)and(與)or(或)xor(異或)等。
在進行陣列壓縮的時候,應該特別重要的一點需要注意,那就是位寬的問題下面就用一個例子來進行說明。

 module test_enum();
    bit on[10];
    int total;
   initial
     begin                 
      foreach(on[i])
      on[i]=i;
      $display("on.sum=%0d",on.sum);         //on.sum是單位元無符號的數
      $display("on.sum=%0d",on.sum+32'd0);  //on.sum是32位元數
      $display("int sum=%0d",on.sum with (int'(item))); //利用with來限定on.sum的資料型別,這是一種比較好用的方式
        
     end     
       endmodule

結果:

 # on.sum=1
 # on.sum=1  //Medesim SE 10.2c模擬出來的結果並不支援相加方式得到的資料轉換
 # int sum=5

SystemVerilog中,對定寬陣列、佇列、動態陣列和關聯陣列可以使用$urandom_range($size(array)-1)來選取隨機一個元素,而對於佇列和動態陣列還可以使用$urandom_range(array.size()-1)。

2.7.2 陣列定位方法

如何選取陣列中最大的值、最小的值?如何選取出陣列中唯一值的佇列?如何對陣列中滿足特定要求的值進行某種操作?下面我們就多這些問題一一作出求解方法。

  module test_enum();
    int f[6]={1,6,2,6,8,6},
         d[]='{2,4,6,8,10},
         q[$]={1,3,5,7},
         tq[$];
   initial
     begin                 
        tq=q.min();     //求最小值
        foreach(tq[i])
        $display("min:tq[%0d]=%0d",i,tq[i]);
        
        tq=q.max();    //求最大值
         foreach(tq[i])
        $display("max:tq[%0d]=%0d",i,tq[i]);
        
        tq=f.unique();   //求陣列中唯一值的佇列
         foreach(tq[i])
        $display("unique:tq[%0d]=%0d",i,tq[i]);
        
        tq=d.find with (item>3);  //利用find函式做操作
         foreach(tq[i])
        $display("find:tq[%0d]=%0d",i,tq[i]);
        tq.delete();     //等價的操作
        foreach(d[i])
          if(d[i]>3)
            tq.push_back(d[i]);
        foreach(tq[i])
        $display("tq[%0d]=%0d",i,tq[i]);
        
        tq=d.find_index with (item>3);  //輸出的是index索引也就是第幾位的值
        foreach(tq[i])
        $display("tq[%0d]=%0d",i,tq[i]);  
        
     end     
     
   endmodule

結果:

# min:tq[0]=1
# max:tq[0]=7
# unique:tq[0]=1
# unique:tq[1]=2
# unique:tq[2]=6
# unique:tq[3]=8
# find:tq[0]=4
# find:tq[1]=6
# find:tq[2]=8
# find:tq[3]=10
# tq[0]=4
# tq[1]=6
# tq[2]=8
# tq[3]=10
# tq[0]=1
# tq[1]=2
# tq[2]=3
# tq[3]=4

我想通過上述的程式碼和註釋,大家都能夠很好地理解。
注意:item被稱為重複引數,它代表了陣列中一個單獨的元素,item是預設的名字,你也可以指定別的名字。下面四種情況是等價的。

 tq=d.find_first with (item==4);
 tq=d.find_first() with (item==4);
 tq=d.find_first(item) with (item==4);
 tq=d.find_first(x) with (x==4);

當陣列的縮減方法和條件語句with結合使用時,sum操作符的結果是條件表示式為真的次數。下面我們來看一個例子。

module test_enum();
    int count,
        total,
        d[]='{9,1,8,3,4,4};
   initial
     begin                 
       count=d.sum with (item>7);  //比較表示式返回0或1
       total=d.sum with ((item>7)*item);
       $display("count=%0d total=%0d",count,total);  //2,17
       
       count=d.sum with (item<8);
       total=d.sum with (item<8?item:0);
       $display("count=%0d total=%0d",count,total);//4,12
       
       count=d.sum with (item==4);
       $display("count=%0d",count); //2
     end     
     
   endmodule
2.7.3 陣列的排序

SystemVerilog有幾個可以改變陣列中元素順序的方法。包括反向、正序、逆序、隨機。

 int d[]='{9,1,8,3,4,4};
 d.reverse();
 d.sort();
 d.rsort();
 d.shuffle();

2.8 資料儲存型別的選擇

其實資料型別的選擇是多方面的,我們要考慮靈活性、儲存器用量、速度、排序和資料結構等多種方面,在我們以後的應用中,我們將會深入地理解每種不同的資料型別的利弊。

2.9 typedef建立新的型別

這是在原有資料型別之上定義新的資料型別。我們為了不混淆,約定所有使用者自定義型別都帶字尾“_t”。
下面我們來看幾個例子。

 parameter opsize=8;
 typedef reg[opsize-1:0] opreg_t;
 opreg_t op_a,op_b;

 typedef bit[31:0] uint;
 typedef int unsigned uint;  //等價的兩種方式

對於新的陣列定義並不是很明顯。你需要把陣列的下標放在新的陣列名稱中。如下面的例子所示:

 typedef int fixed_array5[5];
 fixed_array5 f5;
 initial
    begin
     foreach(f5[i])
             f5[i]=i;
      end

2.10建立使用者自定義結構

Verilog中沒有資料結構,這是它的一個比較大的缺陷。在SystemVerilog中,我們引入了資料結構的概念。struct只是把資料組織在一起,只是一個資料的集合。

2.10.1 使用struct建立新型別

struct可以把若干個變數組合到一起。我們統一將struct建立的新型別用“_s”來表示。

 typedef struct{bit[7:0] r, g,b;} pixel_s;
  pixel_s my_pixel;

  initial 
      begin
         typedef struct {int a,
                                 byte b,
                                 shortint c;} my_struct_s;
          my_struct_s st='{32'haaaaaaaa,
                                     8'hbb,
                                      16'hcccc};
           $display("st=%x %x %x",st.a,st.b,st.c);
         end
2.10.2建立可容納不同型別的聯合

聯合體,通常意義上來講就是同一位置放置不同型別的資料。如果需要以若干不同的格式對同一暫存器進行頻繁讀寫時,聯合體相當有用。我們約定以“_u”為字尾。

 typedef union { int i; real f;} num_u;
 num_u un;
  un.f=0.0;
2.10.3合併結構

通過一個例子我們來描述一下合併結構(packed)可以節省儲存空間。

   typedef struct packed {bit [7:0] r,g,b} pixel_p_s;
   pixel_p_s my_pixel;

2.11型別的轉化

SystemVerilog提供了多種資料型別,在我們實際的應用過程中,我們需要對資料型別進行轉化。本部分就轉化提供了幾種方法,下面一一來介紹。

2.11.1靜態轉換

靜態裝換不對轉換值進行檢查。如果越界的話,我們也不能察覺到。
基本轉換格式:type'(val)

 int i;
 real r;
  i=int '(10.0-0.1);
  r=real '(42);
2.11.2動態轉換

動態轉換函式$cast允許對越界的數值進行檢查,如果不越界返回1,否則返回0。

2.11.3流操作符

流操作符>>和<<用於把其後的資料打包成一個位元流。>>是把資料從左到右變成資料流,<<是把資料從右到左變成資料流。
基本的流操作

int h;
bit [7:0]   b,
             g[4],
             j[4]='{8'ha,8'hb,8'hc,8'hd};
bit [7:0] q,r,s,t;
 initial
    begin
        h={>>{j}};  //0a0b0c0d
        h={<<{j}};  //b030d050
        h={<<byte{j}}; //0d0c0b0a
        b={<<{8'b0011_0101}};//10101100
        b={<<4 {8'b0011_0101}};//0101_0011
        {>>{q,r,s,t}}=j;//將分散到四個位元組變數裡
        h={>>{t,s,r,q}};//將四個位元組集中到h裡
     end

2.12 列舉型別

利用內建函式name()可以得到列舉變數值對應的字串。我們統一用字尾“_e”來表示列舉的資料型別。

  • 定義列舉值
    列舉值預設為從0開始遞增的整數,可以自己定義列舉值。通常在我們把0指給列舉常量,可以避免一些不必要的錯誤。

  • 列舉型別的子程式
    (1)first() 返回第一個列舉變數
    (2)last() 返回最後一個列舉變數
    (3)next() 返回下一個列舉變數
    (4)next(N) 返回以後第N個列舉變數
    (5)prev() 返回前一個列舉變數
    (6)prev(N) 返回以前第N個列舉變數
    遍歷所有的列舉成員(注意對列舉型別值的定義)

     module test_typedef();
         typedef enum{red,green,blue=6,yellow,white,black} colors;
            colors my_colors;
    
            initial
              begin
                 my_colors=my_colors.first;
                do
                begin                                          
                 $display("my_colors=%0d/%s",my_colors,my_colors.name);
                   my_colors=my_colors.next;
               end
              while (my_colors!=my_colors.first);
           end
    
      endmodule
    

結果:

        # my_colors=0/red
        # my_colors=1/green
        # my_colors=6/blue
        # my_colors=7/yellow
        # my_colors=8/white
        # my_colors=9/black
2.12.1列舉型別的轉換

列舉型別的預設型別為雙狀態的int。

  • 可以通過簡單的賦值表示式把列舉變數直接賦值給變數int。

  • 不允許直接把int賦值給列舉變數,這種是出於越界情況的考慮。

      module test_enum();
         typedef enum {RED,BLUE,GREEN} COLOR_E;
          COLOR_E color,c2;
         int c;
        initial
        begin                 
         color=BLUE;
          c=color;
          c++;
            if(!$cast(color,c))
               $display("cast failed for c=%0d",c);
                $display("color is %0d/%s",color,color.name);
               c++;
               c2=COLOR_E'(c);
              $display("c2 is %0d/%s",c2,c2.name);
               if(!$cast(color,c))
               $display("cast failed for c=%0d",c);
          end     
       
     endmodule
    

結果:

  # color is 2/GREEN
  # c2 is 3/
  # cast failed for c=3

$cast(color,c)將int型動態轉化為列舉型別,如果沒有越界返回1,否則返回0;界內(0,1,2),3已經越界了。

2.13 常量

SystemVerilog中支援const修飾符,允許在變數宣告時對其進行初始化,但不能在過程程式碼中改變其值。

     initial 
         begin 
             const byte colon=":";
            ......
         end

2.14 字串

SystemVerilog中的string型別可以用來儲存長度可變的字串。單個位元組是byte型別。字串使用動態的儲存方式,所以不用擔心儲存空間會全部用完。

  module test_enum();
      string s;
       initial
         begin                 
          s="IEEE";
         $display(s.getc(0));
         $display(s.tolower());
      
         s.putc(s.len()-1,"-");
         s={s,"P1800"};
      
        $display(s.substr(2,5));    
        my_log($psprintf("%s %5d",s,42));
        end
   
       task my_log (string message);
        $display("@%0t:%s",$time, message);
        endtask
    endmodule
  • getc(N) 返回位置N上的位元組

  • tolower()返回一個小寫的字串

  • putc(M,C)把位元組C寫到字串的M位上,M必須介於0和len所給出的長度之間。

  • substr(start,end),讀取從位置start到end之間的所有字元。

  • task函式是用來返回一個格式化的臨時字串,並且可以直接傳遞給其他子程式。
    結果:

      #          73   對應字元“I”
      # ieee
      # E-P1
      # @0:IEE-P1800    42
    

相關文章