西洲渡

一只还没迈入IC领域的小菜鸡

SV系统验证(5)——数组

数组类型

非组合型(unpacked)

  • 对于Verilog,数组经常会被用来做数据存储,

    例如 reg [15:0] RAM [0:4095]; //memory array 4096*16-bit

  • SV将Verilog这种声明数组的方式称之为非组合型声明,即其中的成员之间存储数据都是互相独立的。

  • Verilog也不会指定软件去如何存储数组中的成员。

    wire [7:0] table [3:0];// 4*8-bit

image.png

  • SV保留了非组合型的数组声明方式,并且扩展了允许的类型,

    包括event,logic,bit,byte,int,longint,shortreal和real类型。

  • SV也保留了Verilog索引非组合型数组或者数组片段的能力,这种方式为数组以及数组片段的拷贝带来了方便。

    1
    2
    3
    4
    5
    6
    7
    8
    int a1 [7:0][1023:0];  //unpacked array   //8*1024-bit
    int a2 [1:8][1:1024]; //unpacked array 索引的时候高维在左边,低维在右边
    a2 = a1; //copy an entire array
    a2[3] = a1[0]; //copy a slice of an array

    //声明数组(非组合型)的方式,以下两种皆可 (单位元素 数组名 数组大小)
    logic [31:0] data [1024]; //1024*8-bit
    logic [31:0] data [0:1023]; //1024*8-bit

组合型(packed)

  • SV将Verilog的向量作为组合型数组声明方式

    1
    2
    wire [3:0] select;  //4-bit"packed array"
    reg [63:0] data; //64-bit"packed array
  • SV也进一步允许多维组合型数组的声明

    1
    2
    logic [3:0][7:0] data;  //2-D packed array     4*8-bit  (单位元素 数组大小 数组名)
    bit [3:0][7:0] data; //2-D packed array
  • 组合型数组会进一步规范数据的存储方式,而不需要关心编译器或者操作系统的区别

image.png

1个word 大小32-bit 对于二值逻辑来说,32位用一个word存放、对于四值逻辑来说要用两个word存放

  • 组合型(packed)除了可以运用的数组声明,也可以用来定义结构体的存储方式(连续存放

    1
    2
    3
    4
    5
    6
    7
    typedef struct packed{
    logic [7:0] crc;
    logic [63:0] data;
    }data word;
    data word [7:0] darray;//1-D packed array of
    //packed structures
    //连续存放,节省空间、易读取
  • 组合型数组和其数组片段也可以灵活选择,用来拷贝和赋值等

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    logic [3:0][7:0] data;            //2-D packed array 
    wire [31:0] out = data; //whole array
    wire sign = data[3][7]; //bit-select
    wire [3:0] nib = data [0][3:0]; //part-select
    byte high_byte;
    assign high_byte = data[3]; //8-bit slice
    logic [15:0] word;
    assign word = data[1:0]; //2 slices

    int [1:0][2:0] arr [3:0][4:0]; // 四维数组 4*5*2*3 (混合型数组)

数组初始化

  • 组合型(packed)数组初始化时,同向量初始化一致

    1
    2
    3
    logic [3:0][7:0] a = 32'h0;           //vector assignment 
    logic [3:0][7:0] b = {16'hz, 16'h0}; //concatenate operator
    logic [3:0][7:0] c = {16{2'b01}}; //eplicate operator
  • 非组合型(unpacked)数组初始化时,则需要通过 ‘{} 来对数组的每一个维度进行赋值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    int d[0:1][0:3]='{'{7,3,0,5},'{2,0,1,6}};
    //d[0][0]=7
    //d[0][1]=3
    //d[0][2]=0
    //d[0][3]=5
    //d[1][0]=2
    //d[1][1]=0
    //d[1][2]=1
    //d[1][3]=6
  • 非组合型数组在初始化时,也可以类似结构体初始化,通过 ‘{} 和default关键词即可以完成

    1
    int a1 [0:7][0:1023] = '{default:8'h55};

数组赋值

  • 以下是组合型数组的赋值方法

    1
    2
    3
    4
    5
    6
    logic [1:0][1:0][7:0] a; //3-D packed array  2*2*8-bit
    a[1][1][0]=1'b0; //assign to one bit
    a=32'hF1A3C5E7; //assign to full array
    a[1][0][3:0]=4'hF; //assign to a part select
    a[0]=16'hFACE; //assign to a slice
    a={16'bz,16'b0}; //assign concatenation
  • 非组合型数组的数据成员或者数组本身均可以为其赋值

    1
    2
    3
    byte a [0:3][0:3];
    a[1][0]=8'h5; //assign to one element
    a[3]='{'hF, 'hA, 'hC, 'hE}; //assign list of values to slice of the array

数组拷贝

  • 对于组合型数组,由于数组会被视为向量,因此当赋值左右两侧操作数的大小和维度不相同时,也可以做赋值。

  • 如果当尺寸不相同时,则会通过截取或者扩展右侧操作数的方式来对左侧操作数赋值。

    1
    2
    3
    4
    5
    6
    7
    bit [1:0][15:0] a;   //32 bit 2-state vector 
    logic [3:0][7:0] b; //32 bit 4-state vector
    logic [15:0] c; //16 bit 4-state vector
    logic [39:0] d; //40 bit 4-state vector
    b = a; //assign 32-bit array to 32-bit array
    c = a; //upper 16 bits will be truncated
    d = a; //upper 8 bits will be zero filled
  • 对于非组合型数组,在发生数组间拷贝时,则要求做左右两侧操作数的维度和大小必须严格一致。

    1
    2
    3
    logic [31:0] a [2:0][9:0];    //3*10*32-bit
    logic [0:31] b [1:3][1:10]; //3*10*32-bit
    a=b; //Iassign unpacked array to unpacked array 维度相同
  • 非组合型数组无法直接赋值给组合型数组,同样地,组合型数组也无法直接赋值给非组合型数组。

foreach循环结构

  • SV添加foreach循环来对一维或者多维数组进行循环索引,而不需要指定该数组的维度大小

    1
    2
    3
    int sum [1:8][1:3];
    foreach (sum[i,j])
    sum[i][j] = i+j; //initialize array
  • foreach循环结构中的变量无需声明

  • foreach循环结构中的变量是只读的(可以引用不能修改),其作用域只在此循环结构中。

系统函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$dimensions(array_name)     //用来返回数组的维度。

$left(array_name,dimension) //返回指定维度的最左索引值(msb)。
logic [1:2][7:0] word [0:3][4:1];
$left(word,1) //will return 0
$left(word,2) //will return 4
$left(word,3) //will return 1
$left(word,4) //will return 7
//与left()类似的,还有{right,low,high}(array_name,dimension)。

$size(array_name, dimension) //可以返回指定维度的尺寸大小。
$increment(array_name, dimension) //如果指定维度的最左索引值大于或等于最右索引值,那么返回1,否则返回-1。
$bits(expression) //可以用来返回数组存储的比特数目。
wire [3:0][7:0] a [0:15]; //$bits(a)返回512 16*3*7
struct packed {byte tag;
logic [31:0] addr} b;
$bits(b); //返回40

动态数组

  • 与之前的定长数组相比,SV还提供了可以重新确定大小的动态数组。

  • 动态数组在声明时需要使用[ ],这表示不会在编译时为其制定尺寸,而是在仿真运行时来确定。

  • 动态数组一开始为空,而需要使用new[]来为其分配空间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    int dyn[], d2[];  //peclare dynamic arraye 
    initial begin
    dyn = new[5]; //A:Allocate 5 elements 默认5个0
    foreach (dyn[j])
    dyn[j] = j; //B:Initialize the elements
    d2 = dyn; //C:Copy a dynamic array
    d2[0] = 5; //D:Modify the copy
    $display(dyn[0],d2[0]); //E:See both values(0 & 5)

    dyn = new[20](dyn); //F:Allocate 20 ints&copy
    dyn = new[100]; //G:Allocate 100 new ints
    //old values are lost
    logic [31:0] vec1, vec2, vec3;
    vec1 = 'h11223344; //11223344
    vec2 = {'h11, 'h22, 'h33, 'h44}; //00000044
    vec2 = {8'h11, 8'h22, 8'h33, 8'h44}; //11223344

    dyn.delete(); //数组清空操作
    `{}或者new[0] //数组清空操作
    end
  • 内建方法size()可以返回动态数组的大小。

  • 内建方法delete()可以清空动态数组,使其尺寸变为0。

  • 动态数组在声明时也可以完成其初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    bit [70] mask[]='{8'b00000000,
    8'b00000001,
    8'b00000011,
    8'b00000111,
    8'b00001111,
    8'b00011111,
    8'b00111111,
    8'b01111111,
    8'b11111111};

队列

  • 可以在队列的任何位置添加或者删除数据成员。
  • 可以通过索引来访问队列的任何一个成员。
  • 通过[$]来声明队列,队列的索引值从0到$(**最后一个元素为$**)。
  • 可以通过内建方法push_back(val)、push_front(val)、pop_back()和pop_front()来顺序添加或者移除并且获得数成员。
  • 可以通过insert(pos,val)来在指定位置插入数据成员。
  • 可以通过delete()来删除所有数据成员。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    int j=1,
    q2[$] = {3,4}, //Queue literals do not use()
    q[$] = {0,2,3}; //{0,2,3}
    initial begin
    q. insert(1,j); //{0,1,2,3} Insertj before ele #1
    q. delete(1); //{0,2,3} Delete element #1
    //These operations are fast
    g.push_front(6); //{6,0,2,3} Insert at front
    j=g.pop_back(); //{6,0,2} j=3
    q.push_back(8); //{6,0,2,8} Insert at back
    j=q.pop_front; //{0,2,8} j=6
    foreach (q[i])
    $display(q[i]); //print entire queue
    q.delete(); // {} Delete queue end

    int j=1,
    q2[$] = {3,4}, //Queue literals do not use
    q[$] = {0,2,5}; //{0,2,5} initial begin //Result
    q = {q[0],j,q[1:$]}; //{0,1,2,5} Insert 1 before 2
    q = {q[0:2], q2, q[3:$]}; //{0,1,2,3,4,5} Insert queue in q 字符拼接可以,无法用insert插入队列
    q = {q[0],q[2:$1}; //{0,2,3,4,5} Delete elem.#1

    //These operations are fast
    q = {6,g}; //{6,0,2,3,4,5} Insert at front
    j = q[$]; //j=5 pop back g=q[0:$-11;//{6,0,2,3,4} equivalent g={q,8};//{6,0,2,3,4,8} Insert at back
    j = q[0]; //j=6 pop_front g=g[1:$];//{0,2,3,4,8} equivalent
    q = {}; //{} pelete contents
    end

关联数组

由于处理器在访问存储时的访问的随机或者散乱的,这意味个测试中,处理器也许只会访问几百个存储地址,而剩下大的地址都将被初始化为0并且浪费了仿真时的存储空间。

  • 关联数组可以用来存放散列的数据成员。索引类型可以为任何类型,而散列存储的数员也可以为任意类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    initial begin 
    bit [31:0] mem [int unsigned];
    int unsigned data,addr;
    repeat(5)begin
    std::randomize(addr,data)with {addr[31:8]==0; addr[1:0]== 0; data inside {[1:10]};}; //随机生成数据
    $display("address:'h80x,data:'h80x",addr,data);
    mem[addr]=data;
    foreach(mem[idx])
    $display("mem address:'ha0x,data:'h80x", idx, mem[idx]);
    end
    end

缩减方法

  • 基本的数组缩减方法是把一个数组缩减成一个值。
  • 最常用的缩减方法是sum,它对数组中的所有元素求和
    1
    2
    3
    4
    5
    byte b[$]={2, 3, 4, 5};
    int w;
    w=b.sum(); //14=2+3+4+5
    w=b.product(); //120=2*3*4*5
    w=b.and(); //00000000=2&3&4&5其它的数组缩减方法还有product(积),and(与),or xor(异或)

定位方法

  • 对于非合并数组,可以使用数组定位方法,其返回值将是一列而非一个数据成员。

    1
    2
    3
    4
    5
    6
    7
    int f[6]={1, 6, 2, 6, 8, 6};  //Fixed-size array 
    int d[]={2, 4, 6, 8, 10}; //Dynamic array
    int g[$]={1, 3, 5, 7}; //Queue
    tq[$]; //Temporary queue for res
    tq=g.min(); //{1}
    tg=d.max(); //{10}
    tq=f.unique(); //{1,6,2,8}
  • 使用foreach也可以实现数组的搜索,不过使用find…with查找满足条件的数据成员时,更为方便。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    int d[]={9, 1, 8, 3, 4, 4};
    tq[$];
    //Find all elements greater than 3
    tq=d.find with(item>3); //{9,8,4,4}

    tq.delete(); //等价于
    foreach(d[i])
    if(d[i]>3
    tq.push_back(d[i]);

    tg=d.find_index with (item>3); //{0,2,4,5}
    tg=d.find_first with (item>99); //{-none found
    tg=d.find_first_index with (item==8); //{2}d[2]=8
    tg=d.find_last with (item==4); //{4}
    tq=d.find_last_index with (item==4); //{5}d[5]=4

排序方法

  • 可以通过排序方法改变数组中元素的顺序,可以对它们进行逆向或者乱序的排列。
    1
    2
    3
    4
    5
    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)function void push_back(T val),(2)function T pop_front),(3)function void insert(int POS,T val)。可以在module中,通过声明一个动态数组 int array[],并且实现以上三个方法,再通过在initial begin.…end过程块中测试、打印以上方法的结果来完成代码实现和测试。需要提交代码截图和测试的打印结果。