💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] 和verilog相比system verilog提供了很多数据改进的数据结构,一些数据对于创建者和测试者都有用。 ># 内建数据类型 ## 逻辑(logic)类型 system verilog对经典的reg数据类型进行了改进,使他除了作为变量以外,还能被**连续赋值、门单元、和驱动模块所驱动**,这种改进的数据类型被称为**logic**。</br> 它既可被过程赋值也能被连续赋值,编译器可自动推断**logic**是**reg**还是**wire**。唯一的限制是**logic**只允许一个输入,不能被多重驱动,所以**inout**类型端口不能定义为**logic**。不过这个限制也带来了一个好处,由于大部分电路结构本就是单驱动,如果误接了多个驱动,使用**logic**在编译时会报错,帮助发现bug。*所以单驱动时用**logic**,多驱动时用**wire***。</br> 在[Jason的博客](https://link.zhihu.com/?target=http%3A//www.verilogpro.com/verilog-reg-verilog-wire-systemverilog-logic)评论中,Evan还提到一点**logic**和**wire**的区别。**wire**定义时赋值是连续赋值,而**logic**定义时赋值只是赋初值,并且赋初值是不能被综合的。 ```verilog wire mysignal0 = A & B; // continuous assignment, AND gate logic mysignal1 = A & B; // not synthesizable, initializes mysignal1 to the value of A & B at time 0 and then makes no further changes to it. logic mysignal2; assign mysignal2 = A & B; // Continuous assignment, AND gate ``` ``` module tb ; logic [7:0]i; //声明logic结构数据 initial begin i = 0; repeat(10) begin //循环10次 $display("i=%d",i); i = i + 1; end $display("test running!"); end endmodule ``` 仿真结果 ``` Start run at Nov 2 19:42 2020 i= 0 i= 1 i= 2 i= 3 i= 4 i= 5 i= 6 i= 7 i= 8 i= 9 ``` logic不仅能够作为变量,而且可以被连续赋值,门单元和模块所驱动。但是logic不能够被多个结构体驱动。下面这段代码展示了logic的使用方法。 ``` module logic_test(input logic rst_h ); parameter CYCLE = 20; logic q,q_1,d,clk,rst_1; initial begin clk = 0; forever #(CYCLE/2) clk = ~clk; // 过程赋值 end assign rst_1 = ~ rst_h; //连续赋值 not n1(q_1,q); //q_1被门驱动 my_dff d1(q,d,clk,rst_1); //q被模块驱动 endmodule ``` **SystemVerilog logic的使用方法** * 单驱动时**logic**可完全替代**reg**和**wire**,除了Evan提到的赋初值问题。 * 多驱动时,如**inout**类型端口,使用**wire**。 ## 双状态数据类型 相比于verilog中的4状态数据类型(1,0,x,z),SV引入双状态数据类型有利于提高仿真器的性能并减少内存的使用量。下面逐例解释。 ``` bit b; // 双状态 单bit bit [31:0] b32; // 双状态,32bit,无符号 int unsigned ui; // 双状态,32bit,无符号 int i; // 双状态,32bit,有符号 byte b8; // 双状态,8bit ,有符号 shortint s; // 双状态,16bit,有符号 longint l; // 双状态,64bit,有符号 integer i4; // 四状态,32bit,有符号 time t; // 四状态,64bit,无符号 real r; // 双状态,双精度浮点数。 ``` 双状态数据类型有利于提高仿真器的性能并减少内存。</br> **对四态信号的检查:**($isunknown) 这会让任意位置出现X或者Z时返回1,使用实例如下: ``` if( $isunknown(iport) == 1) $display("@%0t: 4-state velue detected on iport %b",$time,iport) ``` ***** 部分仿真结果如下: ``` module tb ; longint i; //声明logic结构数据 byte b8; initial begin i = 0; b8 = 2; repeat(3) begin //循环 $display("i=%0d , b8=%0d",i,b8); i = i - 1; b8=b8*2; end end endmodule ``` ``` Start run at Nov 2 20:01 2020 i=0 , b8=2 i=-1 , b8=4 i=-2 , b8=8 ``` ># 定宽数组 verilog要求在声明中必须给出数组的上下边界,因为几乎所有的数组都是用0作为索引下界,**所以sv允许只给出数组的宽度**,跟c语言类似。</br> **例 :数组的声明** ``` int arr[0:15]; //16个整数 int c_style[16]; //c风格,16个整数 ``` 可以通过**在变量后面指定维度的方式来声明多维数组**;</br> **例 : 声明8行4列多维数据** ``` int arr2 [0:7][0:3]; //完整声明 int arr2_cstyle[8][4]; ``` **特别注意:**如果读取的数据地址越界,那么sv会**返回数组元素的缺省值** * logic类型:返回 X * 双状态类型:返回 0 * 线网在没有驱动时输出为 Z ## 数组的遍历 ``` module tb ; int md[2][3] = '{'{1,2,3},'{4,5,6}}; // 声明2行3列数组并赋初值,这里使用常量数组来初始化md数组 initial begin foreach(md[i,j]) //遍历数组 $display("md[%0d][%0d]=%0d",i,j,md[i][j]); end endmodule ``` 输出结果 ``` VCS Build Date = May 24 2016 20:38:43 Start run at Nov 2 20:26 2020 md[0][0]=1 md[0][1]=2 md[0][2]=3 md[1][0]=4 md[1][1]=5 md[1][2]=6 ``` 按维度遍历 ``` module tb ; int md[2][3] = '{'{1,2,3},'{4,5,6}}; // 声明2行3列数组并赋初值 initial begin foreach( md[i] ) begin //遍历第1维 $write("%2d:",i); foreach( md[,j] ) //遍历第2维 $write("%3d",md[i][j]); $display;//显示一个维度 end end endmodule ``` 结果 ``` 0: 1 2 3 1: 4 5 6 ``` ## 数组的比较与复制 ``` module tb ; bit [31:0] src[5] = '{0,1,2,3,4}, dst[5] = '{5,4,3,2,1}; initial begin //比较两个数组 if( src == dst ) $display("src == dst"); else $display("src != dst"); //把src元素拷贝到dst dst = src; //只改变一个元素 dst[0] = 5; //所有元素的值是否相等 $display("src %s dst",(src == dst)? "==" : "!="); //使用数组片段对第 1-4 个元素进行比较 $display("src[1:4] %s dst[1:4]",(src[1:4] == dst[1:4]) ? "==" : "!="); end endmodule ``` 输出结果 ``` Start run at Nov 2 20:41 2020 src != dst src != dst src[1:4] == dst[1:4] ``` ## 使用数组位下标和数组下标 **例 : 位下标和数组下标** ``` module tb ; bit [31:0] src[5] = '{5,1,2,3,4}; initial begin $display(src[0],, // 5 src[0][0],, // 1 src[0][2:1]); // 2 end endmodule ``` ## 合并数组 对于一些数据你可能既希望作为**整体**访问,也希望**拆分为更小单元**访问,使用sv的合并数组可以实现这个功能。</br> **例 : 合并数组例子** ``` module tb ; bit [3:0][7:0] bytes ; //四个字节组装而成的32 bit数据 initial begin bytes = 32'hCafe_Dada; $display(bytes,, //显示所有32 bit bytes[3],, //显示最高字节 "CA" bytes[3][7]); //显示最高bit位 ”1“ end endmodule ``` 结果 ``` 3405699802 202 1 ``` **扩展说明:** 已知一个多维混合数组的定义为: ``` bit [3:0][7:0][15:0] Array [3:0][7][6]; ``` 那么当我们写下 ``` Array[2][3][2][2] = xxxx; ``` 的时候,到底是对哪个位置赋值了?? 话不多说,直接看解答好啦~最后的答案其实很简单,因为有一个简单的图示估计很多人知道,就是逆时针索引法: ![](https://img.kancloud.cn/df/ee/dfee598075719706775ec92a8906638d_533x330.png) **合并数组和非合并数组的选择** * 合并数组:和标量进行相互转换,等待数组中的变化必须使用合并数组 > # 动态数组 sv提供动态数组,动态数组在声明时使用空下标[ ]。**宽度将不会在编译时给出,而是在程序运行时在指定**。数组开始时为空,因此你必须调用`new [ ] `操作来分配空间。</br> **例 :动态数组实例** ``` module tb ; int dyn[],d2[] ; //声明动态数组 initial begin dyn = new[5] ;//分配5个元素空间 foreach( dyn[i] ) dyn[i] = i; //对5个元素空间的值进行初始化 d2 = dyn ; //复制数组dyn到d2 d2[0] = 5 ; //修改d2[0]的值为5 $display(dyn[0],d2[0]); //显示数值 dyn = new[20](dyn); //分配20个证书值并进行复制 dyn = new[100]; //分配 100 个新的整数值 dyn.delete(); //删除所有元素 end endmodule ``` **例 : 使用动态数组保存元素数量不定的列表** ``` bit [7:0] mask[] = '{ 8'h00,8'h01, 8'h03,8'h04, 8'h23,8'h36}; ``` ># 队列 队列与链表类似,可以**在一个队列中的任何地方增加和删除元素**,这类操作在性能上的损失比动态数组小的多!</br> 队列的声明使用`[$]`,队列元素的编号从0到$。sv的队列类似与STL的双端队列。可以通过增加元素来创建队列。**你可以扩大和缩小队列,但是不用向动态数组一样付出很大的代价**。</br> **例 : 队列操作** ``` module tb ; //注意,队列常量不需要使用“’” int q2[$] = {3,4}; int q[$]= {0,2,5}; initial begin //在2之前插入1 q.insert(1,1); // 0 1 2 5 foreach(q[i])$write("%3d",q[i]); $display; //显示结果 //在5之前插入队列q2 q.insert(3,q2); // 0 1 2 3 4 5 foreach(q[i])$write( "%3d",q[i]); $display; //显示结果 //删除q下标为1的元素 q.delete(1); //0 2 3 4 5 foreach(q[i])$write( "%3d",q[i]); $display; //显示结果 //以下操作速度很快 //在队列最前面插入 8 q.push_front(8); //8 0 2 3 4 5 foreach(q[i])$write( "%3d",q[i]); $display; //打印整个链表 //在队列最前后面插入 6 q.push_back(6); //8 0 2 3 4 5 6 foreach(q[i])$write( "%3d",q[i]); $display; //显示结果 //读出队列最前面的元素并从队列中移除该元素 $display(q.pop_front()); foreach(q[i])$write( "%3d",q[i]); $display; //显示结果 0 2 3 4 5 6 //读出队列最后面的元素并从队列中移除该元素 $display(q.pop_back()); foreach(q[i])$write( "%3d",q[i]); $display; //显示结果 0 2 3 4 5 end endmodule ``` 结果 ``` Start run at Nov 2 21:56 2020 0 1 2 5 0 1 2 3 4 5 0 2 3 4 5 8 0 2 3 4 5 8 0 2 3 4 5 6 8 0 2 3 4 5 6 6 0 2 3 4 5 ``` >>**说明:** 可以使用字下标串联来代替方法。对于队列`q[$]={0,2,4}`,如果把`$`放在一个范围表达式的左边,那么`$`将代表最小值,例如`[$:2]`就代表`[0:2]`。如果把`$`放在一个范围表达式的右边,那么`$`将代表最大值,例如`[1:$]`就代表`[1:2]`。 使用上述方法的操作如下:</br> **例 : 队列串联表达式** ``` module tb ; //注意,队列常量不需要使用“’” int q2[$] = {3,4}; int q[$]= {0,2,5}; initial begin //在2之前插入1 q={q[0:1],1,q[2:$]}; // 0 1 2 5 foreach(q[i])$write("%3d",q[i]); $display; //显示结果 //在5之前插入队列q2 q={q[0:2],q2,q[3:$]}; // 0 1 2 3 4 5 foreach(q[i])$write( "%3d",q[i]); $display; //显示结果 //删除q下标为1的元素 q.delete(1); //0 2 3 4 5 foreach(q[i])$write( "%3d",q[i]); $display; //显示结果 //删除整个队列 q={}; end endmodule ``` ># 关联数组 当你只需要偶尔创建一个大容量数组,那么动态数组已经足够好用了,但是如果需要超大容量的呢?sv提供了关联数组用来保存稀疏矩阵的元素。也就是说只为实际写入的数据开辟存储空间。**可以将其理解为哈希表**,虽然哈希表带来了额外开销,但是在这种情况下,这是可以也接受的。</br> **例 : 关联数组声明,初始化和使用** ``` module tb ; bit [63:0] assoc[ bit [63:0] ],idx = 1; initial begin //对稀疏分布的元素进行赋值 repeat (3) begin assoc[idx] = idx; idx = idx << 1; end //foreach遍历数组 foreach(assoc[i])$display("assoc[%h]=%h",i,assoc[i]); $display("+++++++++++++++++++++++"); //使用函数遍历数组 if( assoc.first(idx) ) begin do $display("assoc[%h]=%h",idx,assoc[idx]); while( assoc.next(idx) ); //得到下一个索引 end //找到并删除第一个元素 assoc.first(idx); assoc.delete(idx); $display("The array now has %0d elements",assoc.num); end endmodule ``` 结果 ``` Start run at Nov 2 22:22 2020 assoc[0000000000000001]=0000000000000001 assoc[0000000000000002]=0000000000000002 assoc[0000000000000004]=0000000000000004 +++++++++++++++++++++++ assoc[0000000000000001]=0000000000000001 assoc[0000000000000002]=0000000000000002 assoc[0000000000000004]=0000000000000004 The array now has 2 elements ``` **例 : 使用带字符串索引的关联数组** ``` //关联数组也可以用字符串索引进行寻址,使用字符串索引读取文件,并建立关联数组switch,可以实现字符串到数字的映射。 /* 输入文件内容如下: 42 min_address 1492 max_address */ int switch[string],min_address,max_address; //定义变量 initial begin //初始化 int i,r,file; string s;//string 用来年保存长度可变的字符串常量 file = $fopen(“switch.txt”,r); //在当前目录下以只读的方式打开文件switch.txt,使用指针向量file指向该文件 while(! $feof(file) ) begin //未读取到文件的结尾时 r=$fscanf (file, “%d %s”, i, s); //将文件中的数字以10进制的方式保存到i中,字符串保存到s中 switch[s] = i; //为数组中的字符串指定地址 end $fclose(fire); //关闭文件 //获取最小地址值,缺省为0 min_address=switch[“min_address”] //将min_address对应的地址(数字)赋值给min_address //获取最大地址值,缺省为1000 if(switch.exists(“max_address”)) //使用exists函数判断switch数组中是否含有“max_address” max_address = switch[“max_address”]//含有的话,将内容的地址赋值给 max_address else max_address =1000 //不存在指定内容,则默认地址为1000 //打印数组的所有元素 foreach(switch [s]) $display(“switch[ ‘ %s ’ ] = %0d ”, s ,switch[s] );//打印数组的内容和相应的地址 end ``` --- **例 : 字符关联数组操作** ``` module tb ; bit [63:0] assoc[ string ]; initial begin //对稀疏分布的元素进行赋值 assoc["a"] = 1; assoc["b"] = 2; assoc["c"] = 3; //foreach遍历数组 foreach(assoc[i])$display("assoc[%s]=%h",i,assoc[i]); end endmodule ``` ># 链表 sv提供了链表数据结构,类似于STL的列表容器,这个容器被定义为参数化的类,可以根据用户所需存放各种类型的数据。</br> 虽然sv提供了链表,但是应该避免使用它。sv使用队列更高效。 ># 数组的方法 sv提供了很多数组的方法,可以用于任何一种非合并数组的类型,**包括定宽数组,动态数组,队列和关联数组**。 ## sum方法:数组求和 **例 : 数组求和** ```module tb ; bit on[5] = '{0,1,0,1,0};//单bit数组 initial begin foreach( on [i])$write("%3d",on[i]);$display; //单bit求和 $display("on.sum=%0d",on.sum); end endmodule ``` 结果 ``` Start run at Nov 2 22:49 2020 0 1 0 1 0 on.sum=0 ``` >>sum方法的结果位宽和数组定义的位宽是一致的。 ## product方法:数组求积 **例 :数组求积** ``` module tb ; int on[3] = '{1,2,3};//单bit数组 initial begin foreach( on [i])$write("%3d",on[i]);$display; //单bit求和 $display("on.product=%0d",on.product); end endmodule ``` 结果 ``` Start run at Nov 2 22:57 2020 1 2 3 on.product=6 ``` ##and,or,xor方法:数组求与,或,异或 **例 :数组求与,或,异或** ``` module tb ; int on[3] = '{1,3,3};//单bit数组 initial begin foreach( on [i])$write("%3d",on[i]);$display; $display("on.and=%0d",on.and); $display("on.or=%0d",on.or); $display("on.xor=%0d",on.xor); end endmodule ``` ## min,max方法:最大值最小值方法 **注意返回值为一个队列!** **例 :最大值最小值方法** ``` module tb ; int on[] = '{1,3,3,9}; int rt[$]; initial begin foreach( on [i])$write("%3d",on[i]);$display; rt=on.min();foreach(rt[i])$write("%3d",rt[i]);$display; rt=on.max();foreach(rt[i])$write("%3d",rt[i]);$display; end endmodule ``` 结果 ``` Start run at Nov 2 23:11 2020 1 3 3 9 1 9 ``` ## unique方法:排除重复数值 **注意返回值为一个队列!** **例 :排除重复数值** ``` module tb ; int on[] = '{1,3,3,9}; int rt[$]; initial begin foreach( on [i])$write("%3d",on[i]);$display; rt=on.unique();foreach(rt[i])$write("%3d",rt[i]);$display; end endmodule ``` 结果 ``` Start run at Nov 2 23:15 2020 1 3 3 9 1 3 9 ``` ## size方法:获取数组大小 **例 :获取数组大小** ``` module tb ; int on[] = '{1,3,3,9}; int rt[$]; initial begin foreach( on [i])$write("%3d",on[i]);$display; //获取动态数组的大小 $display("len=%0d",on.size()); //获取各种数组大小的方法 $display("len=%0d",$size(on)); endmodule ``` 结果 ``` Start run at Nov 2 23:18 2020 1 3 3 9 len=4 len=4 ``` ## find方法:数组定位方法 **注意返回值为一个队列!** **例 :find数组定位方法** ``` module tb ; int on[] = '{4,1,3,3,9}; int rt[$]; initial begin foreach( on [i])$write("%3d",on[i]);$display; //找出所有大于等于3的元素 4 3 3 9 rt=on.find with (item >= 3); foreach(rt[i])$write("%3d",rt[i]);$display; //找出值大于等于2的索引 0 2 3 4 rt=on.find_index with (item >= 2); foreach(rt[i])$write("%3d",rt[i]);$display; //找出第一个大于4的值 9 rt=on.find_first with (item >4); foreach(rt[i])$write("%3d",rt[i]);$display; //找出最后一个小于9的值 3 rt=on.find_last with (item < 9); foreach(rt[i])$write("%3d",rt[i]);$display; //找出第一个大于4的数据的索引值 4 rt=on.find_first_index with (item >4); foreach(rt[i])$write("%3d",rt[i]);$display; //找出最后一个==3的数据的索引值 rt=on.find_last_index with (item == 3); foreach(rt[i])$write("%3d",rt[i]);$display; end endmodule ``` 结果 ``` Start run at Nov 2 23:31 2020 4 1 3 3 9 4 3 3 9 0 2 3 4 9 3 4 3 ``` **例 : 等同的几种描述** ``` rt=on.find_first with (item >= 3); rt=on.find_first() with (item >= 3); rt=on.find_first(item) with (item >= 3); rt=on.find_first(x) with (x >= 3); ``` ## 数组排序方法:reverse,sort,rsort,shuffle **例:数组排序** ``` module tb ; int on[] = '{4,1,3,3,9}; int rt[$],msum; initial begin foreach( on [i])$write("%3d",on[i]);$display; //数组反序 on.reverse(); foreach( on [i])$write("%3d",on[i]);$display; //数组 小->大 on.sort(); foreach( on [i])$write("%3d",on[i]);$display; //数组 大->小 on.rsort(); foreach( on [i])$write("%3d",on[i]);$display; //数组洗牌 on.shuffle(); foreach( on [i])$write("%3d",on[i]);$display; end endmodule ``` 结果 ``` Start run at Nov 3 00:12 2020 4 1 3 3 9 9 3 3 1 4 1 3 3 4 9 9 4 3 3 1 3 4 1 9 3 ``` ## 结构数组的使用和排序 **例 : 结构数组的使用和排序例子** ``` module tb ; int rt[$],msum; struct packed {byte red,green,blue;} c[]; //结构数组 initial begin c=new[5]; foreach(c[i])c[i] = $urandom;//随机赋值 foreach( c[i])$write("%10d",c[i].red);$display; c.sort with (item.red); //up排序red foreach( c[i])$write("%10d",c[i].red);$display; end endmodule ``` 结果 ``` Start run at Nov 3 00:17 2020 -30 -34 127 -86 121 -86 -34 -30 121 127 ``` > # typedef创建新的数据类型 **例 :创建新数据类型** ``` parameter N = 8; typedef reg [N - 1 : 0 ] opreg_t; opreg_t c; ``` > # struct创建新的数据类型 **例 :struct创建新数据类型** ``` module tb ; int rt[$]; typedef struct packed {byte red,green,blue;} pixel_s;//结构数组 typedef struct packed {int a;bit[2:0] b;} m_s;//结构数组 pixel_s c='{1,2,3}; m_s cc = '{ 32'd23, 3'b101 }; initial begin $display("%5d %5d %5d",c.red,c.green,c.blue); $display("%5d %5d",cc.a,cc.b); end endmodule ``` 结果 ``` Start run at Nov 3 00:38 2020 1 2 3 23 5 ``` ># 静态转换 静态转换操作不对转换值进行检查。 **例 : 静态转换例子** ``` module tb ; int rt[$]; int i ; real r; initial begin i = int '(10.0 - 0.1); //非强制转换 r = real '(42); $display(i); $display(r); end endmodule ``` > # 流操作 ``` module tb ; int rt[$]; byte h ; bit [3:0] q[2] = '{4'h04,4'h01} ; initial begin $display("%6b %6b",q[0],q[1]); //将q打包为byte h = {>>{q}}; //0100_0001 $display("%b",h); //位倒叙 h = {<<{h}}; //1000_0010 $display("%b",h); end endmodule ```