SV学习记录(二) 目录2.1 内建数据类型2.2 定宽数组聚合运算位下标和数组下标合并数组合并or非合并2.3 动态数组2.4 队列2.5 关联数组2.6 链表2.7 数组的方法array methods数组的缩减方法数组定位方法数组的排序使用数组定位方法建立计分板2.8 选择存储类型灵活性存储器用量速度排序2.9 使用typedef创建新的的类型2.10 创建用户自定义结构用struct创建新类型对结构进行初始化联合体union合并结构packed合并or非合并2.11 类型转换静态转换动态转换流操作符2.1 内建数据类型Logic。logic信号只能有一个驱动如果有多个会报错Sv中有一些双状态数据类型正常verilog中有四状态的10XZ但是下面的数据类型有的只有两个状态。注意这些数据类型本身是带符号的bit b; // 双状态单比特 bit [31:0] b32; // 双状态32 比特无符号整数 int unsigned ui; // 双状态32 比特无符号整数 int i; // 双状态32 比特有符号整数 byte b8; // 双状态8 比特有符号整数 shortint s; // 双状态16 比特有符号整数 longint l; // 双状态64 比特有符号整数 integer i4; // 四状态32 比特有符号整数 time t; // 四状态64 比特无符号整数 real r; // 双状态双精度浮点数双状态可能存在的问题是如果用它接收四状态输出XZ会被转换为0/1无法正常观测。解决方案if ($isunknown(iport) 1) $display (%0t: 4-state value detected on iport %b, $time, iport); ​ 使用格式符 %0t 和参数 $time 可以打印出当前的仿真时间打印的格式在 $timeformat()子程序中指定。3.7 节中有关于时间值的详细介绍。2.2 定宽数组声明变得更简单int lo_hi[0:15]; // 16 个整数 [0]...[15] int c_style[16]; // 16 个整数 [0]...[15]sv中还有多维数组。从左向右第一个是行第二个是列。int array2 [0:7][0:3]; // 完整的声明 int array3 [8][4]; // 紧凑的声明 array2[7][3] 1; // 设置最后一个元素如果访问越界对于logic会返回X双状态会返回0无驱动的线网会返回Z。常量数组。如何给数组赋值打上’{}用于表示赋值。int ascend[4] {0,1,2,3}; // 对 4 个元素进行初始化 int descend[5]; ​ descend {4,3,2,1,0}; // 为 5 个元素赋值 descend[0:2] {5,6,7}; // 为前 3 个元素赋值 ascend {4{8}}; // 四个值全部为 8 descend {9,8,default:1}; // {9,8,1,1,1}foreach遍历多维数组。注意在遍历的时候不是写作m[i] [j]而是m[i,j]int md[2][3] { { {0, 1, 2}, {3, 4, 5} } }; ​ initial begin $display(Initial value:); foreach (md[i,j]) // 这是正确的语法格式 $display(md[%0d][%0d]%0d, i, j, md[i][j]); ​ $display(New value:); // 对最后三个元素重复赋值 5 md { { {9, 8, 7}, {3{32d5}} } }; foreach (md[i,j]) // 这是正确的语法格式 $display(md[%0d][%0d]%0d, i, j, md[i][j]); end如果在遍历的时候只想遍历一个维度那就写作m[i]默认从第一个维度开始遍历。foreach的遍历方式和数组的定义方式有关。如果数组定义为f[5]/f[0:4]foreachf[i]等同于int f1[5]; int f2[4:0]; ​ foreach(f1[i]) for(int i0; i4; i) foreach(f2[i]) for(int i4; i0; i--)聚合运算指的是把两个相同形状的数组进行比较只能比相等/不等和赋值。其他操作如1等不能通过这种方式实现。initial begin bit [31:0] src[5] {0,1,2,3,4}, dst[5] {5,4,3,2,1}; ​ // 两个数组的聚合比较 if (src dst) $display(src dst); else $display(src ! dst); ​ // 把 src 所有元素值复制给 dst dst src; ​ // 使用数组片段对第 1-4 个元素进行比较 $display(src[1:4] %s dst[1:4], (src[1:4] dst[1:4]) ? : !); end再注意一个小点在display中可以用三目运算符选择字符串。位下标和数组下标位于变量名左侧的是位域表示每个变量的位宽右侧的是数组域表示个数。在索引的时候如果只打一个[]默认为索引数组域打第二个[]可以索引位域initial begin bit [31:0] src[5] {(5{5})}; $displayb (src[0],, // b101 或d5 src[0][0],, // b1 src[0][2:1]); // b10 end合并数组首先解释下方barray数组的理解方法。[3]表示是三个元素组成的非合并数组[3:0]表示每个元素内部由4个小元素[7:0]组成[7:0]是位域。非合并的含义是在存储的时候不一定存储在一起。合并的含义是一定会存储在一起。bit [3:0] [7:0] barray [3]; // 合并3×32 比特 bit [31:0] lw 32h0123_4567; // 字 bit [7:0] [3:0] nibbles; // 合并数组 barray[0] lw; barray[0][3] 8h01; barray[0][1][6] 1b1; nibbles barray[0]; // 复制合并数组的元素值合并or非合并当你需要和标量进行相互转换时使用合并数组会非常方便。例如你可能需要以字节或字为单位对存储单元进行操作。如果你需要等待数组中的变化则必须使用合并数组。比如(barray[0])。注意不能写(barray)因为只适用于合并数组。2.3 动态数组生成随机向量时事务大小不一定为了避免浪费存储空间可以使用动态数组。数组宽度在编译时不必给出在程序运行时再指定。new[]是函数用于给动态数组指定空间大小。new[20] (dyn)表示给dyn开一个20大小的数组再把原来的dyn的值赋给前五个new[100]表示给dyn开100大小的数组不复制int dyn[], d2[]; // 声明动态数组 ​ initial begin dyn new[5]; // A: 分配 5 个元素 foreach (dyn[j]) dyn[j] j; // B: 对元素进行初始化 d2 dyn; // C: 复制一个动态数组 d2[0] 5; // D: 修改复制值 $display(dyn[0], d2[0]); // E: 显示数值 (0 和 5) dyn new[20](dyn); // F: 分配 20 个整数值并进行复制 dyn new[100]; // G: 分配 100 个新的整数值 // 旧值不复存在 dyn.delete();// H: 删除所有元素 end只要数据类型相同定宽数组和动态数组可以互相赋值。如果给动态数组赋定宽数组系统会自动调用new[]。动态数组还可以像下面这么用只知道每个元素的大小没统计有几个元素bit[7:0] mask[] {8b0000_0000, 8b0000_0001, 8b0000_0011, 8b0000_0111, 8b0000_1111, 8b0001_1111, 8b0011_1111, 8b0111_1111, 8b1111_1111};2.4 队列如果在动态数组里删除/添加一个元素系统会调用new为它重新分配空间开销太大。队列可以解决这个问题。队列的声明是[$]insert函数左侧的数字表示插入在第几个元素的前面第几个是从0开始数的。右侧是插入内容。delete索引的也是元素序号而不是内容例 2.19 队列的操作 ​ int j 1, q2 [ $ ] { 3, 4 }, // 队列的常量不需要使用“” q [ $ ] { 0, 2, 5 }; // { 0, 2, 5 } ​ initial begin q.insert(1, j); // { 0, 1, 2, 5 } 在 2 之前插入 1 q.insert(3, q2); // { 0, 1, 2, 3, 4, 5 } 在 q 中插入一个队列 q.delete(1); // { 0, 2, 3, 4, 5 } 删除第 1 个元素 ​ // 下面的操作执行速度很快 q.push_front(6); // { 6, 0, 2, 3, 4, 5 } 在队列前面插入 j q.pop_back; // { 6, 0, 2, 3, 4 } j 5 q.push_back(8); // { 6, 0, 2, 3, 4, 8 } 在队列末尾插入 j q.pop_front; // { 0, 2, 3, 4, 8 } j 6 foreach (q[i]) $display(q[i]); // 打印整个队列 q.delete(); // {} 删除整个队列 end如果不用系统自带函数就像下面这么写$放在冒号:左边表示索引的最小值放在右边表示最大值。例 2.20 队列操作 ​ int j 1, q2[$] {3, 4}, // 队列的常量不需要使用“” q[$] {0, 2, 5}; // {0, 2, 5} ​ initial begin // 结果 q {q[0], j, q[1:$]}; // {0, 1, 2, 5} 在 2 之前插入 1 q {q[0:2], q2, q[3:$]}; // {0, 1, 2, 3, 4, 5} 在 q 中插入一个队列 q {q[0], q[2:$]}; // {0, 2, 3, 4, 5} 删除第 1 个元素 // 下面的操作执行速度很快 q {6, q}; // {6, 0, 2, 3, 4, 5} 在队列前面插入 j q[$]; // 等同于 j 5 q q[0:$ - 1]; // {6, 0, 2, 3, 4} 从队列末尾取出数据 q {q, 8}; // {6, 0, 2, 3, 4, 8} 在队列末尾插入 j q[0]; // 等同于 j 6 q q[1:$]; // {0, 2, 3, 4, 8} 从队列前面取出数据 ​ q {}; // {} 删除整个队列 end可以把定宽或动态数组的值复制给队列。队列中的元素是连续存放的。相应操作所耗费的时间会随着队列的大小线性增加。2.5 关联数组如果要存储的数据量巨大但是访问的不多那么可以只为写入的数据分配空间。assoc.first(idx)会把第一个有效变量的索引传递给idx。initial begin ​ bit[63:0] assoc[bit[63:0]], idx 1; ​ // 对稀疏分布的元素进行初始化 repeat (64) begin assoc[idx] idx; idx idx 1; end ​ // 使用 foreach 遍历数组 foreach (assoc[i]) $display(assoc[%h] %h, i, assoc[i]); ​ // 使用函数遍历数组 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);下面展示另一种int switch[string], min_address, max_address; initial begin int i, r, file; string s; file $fopen(switch.txt, r); while (! $feof(file)) begin r $fscanf(file, %d %s, i, s); switch[s] i; end $fclose(file);由此可以看出关联数组的特点string是关联数组中的键int表示的是每个键对应的值每当从文件中读出一个键会对应为它分配一个值。2.6 链表不建议使用2.7 数组的方法array methods数组的缩减方法缩减指的是把数组中的许多数缩减成一个数输出其实就是通过某种运算实现能够窥见数组中的一部分特性。运算通常包括和积与或异或等等下面.sum就是一个求和函数。需要注意的是这里体现了位宽扩展问题正常来讲on是一个元素为1bit的非合并数组它的和也会是1bit但是如果它出现在的表达式中有高bit数它就会自动扩展完再加和。例 2.23 数组求和 ​ bit on[10]; // 单比特数组 int total; ​ initial begin foreach (on[i]) on[i] i; // on[i] 的值为 0 或 1 ​ // 打印出单比特和 $display(on.sum%0d, on.sum); // on.sum 1 ​ // 打印出 32 比特和 $display(on.sum%0d, on.sum 32d0); // on.sum 5 ​ // 由于 total 是 32 比特变量所以数组和也是 32 比特 total on.sum; $display(total%0d, total); // total 5 ​ // 将数组和与一个 32 比特数进行比较 if (on.sum 32d5) // 条件成立 $display(sum has 5 or more 1s); ​ // 使用带 32 比特有符号运算的 with 表达式 $display(int sum %0d, on.sum with (int(item))); end如果想从数组中随机选择出一个元素是没有办法直接取出的只能像树一样通过foreach逐个向下走找到。如下所示例 2.24 从一个关联数组中随机选取一个元素 ​ int aa[int], rand_idx, element, count; ​ element $urandom_range(aa.size() - 1); foreach(aa[i]) if (count element) begin rand_idx i; // 保存关联数组的索引 break; // 并退出 end ​ $display(%0d element aa[%0d]%0d, element, rand_idx, aa[rand_idx]);数组定位方法如何在非合并数组中定位到我想找的数比如最大最小等unique是排除重复元素例 2.25 数组定位方法: min, max, unique ​ 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}还可以用find和with搭配找到with后面的是条件。item表示d中满足条件的元素如果是find返回的是元素如果是find_index返回的是索引从0开始。int d[]{9,1,8,3,4,4},tq[$]; // 找出所有大于 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]); ​ tq d.find_index with (item 3); // {0, 2, 4, 5} tq d.find_first with (item 99); // {} - 没有找到 tq d.find_first_index with (item 8); // {2} d[2] 8 tq d.find_last with (item 4); // {4} tq d.find_last_index with (item 4); // {5} d[5] 4下面是几种求和方法。如果只有判断条件比如item 7item 4等这种求和是拿判断结果求和满足条件的是1不满足的是0这相当于给求和加了约束条件可以通过结果看出数组的特点。例 2.28 数组定位方法 ​ int count, total, d[] {9,1,8,3,4,4}; ​ count d.sum with (item 7); // 2: {9,8} total d.sum with ((item 7) * item); // 17 9 8 count d.sum with (item 8); // 4: {1,3,4,4} total d.sum with (item 8 ? item : 0); // 12 1 3 4 4 count d.sum with (item 4); // 2: {4,4}数组的排序常见不能带with的reverse反转shuffle打乱可以带with的sort顺序rsort倒序。例 2.30 对结构数组进行排序 ​ struct packed {byte red, green, blue;} c[]; initial begin c new[100]; // 分配 100 个像素 foreach(c[i]) c[i] $urandom; // 填上随机数 ​ c.sort with (item.red); // 只对红色(red)像素进行排序 ​ // 先对绿色(green)像素后对蓝色(blue)像素进行排序 c.sort(x) with ({x.green, x.blue}); end使用数组定位方法建立计分板scb就是计分板在验证中是我们预期得到的结果每当DUT计算完毕传来一个addr就以addr为元素进行索引找到了就删除没找到就报错例 2.31 带数组方法的记分板 ​ typedef struct packed { bit[7:0] addr; bit[7:0] pr; bit[15:0] data; } Packet; ​ Packet scb[$]; ​ function void check_addr(bit[7:0] addr); int intq[$]; ​ intq scb.find_index() with (item.addr addr); case (intq.size()) 0: $display(Addr %h not found in scoreboard, addr); 1: scb.delete(intq[0]); default: $display(ERROR: Multiple hits for addr %h, addr); endcase endfunction : check_addr2.8 选择存储类型灵活性如果数组索引有规律就选定宽数组动态数组如果数组稀疏没有规律就选关联数组如果保存的元素数目前后变化很大选队列因为需要频繁添加/删除元素队列的维护最简单存储器用量双状态存储用量更小对于一千个元素以下数组类型影响不大对于一千个以上定宽数组和动态数组比较好速度定宽数组和动态数组一样最快队列和前两个差不多但是要在中间插入新元素比较困难关联数组的查找方式是树最慢排序如果元素全都一次性加进去用定宽/动态如果频繁加入/删除用队列如果数组值互斥且不连续可以用关联数组方便查找2.9 使用typedef创建新的的类型和C中一样的用法例 2.33 SystemVerilog 中用户自定义类型 parameter OPSIZE 8; typedef reg [OPSIZE-1:0] opreg_t; opreg_t op_a, op_b;2.10 创建用户自定义结构用struct创建新类型对结构进行初始化用typedef方便后面还能用。赋值直接用赋值语句就行。例 2.38 对 struct 类型进行初始化 ​ initial begin typedef struct { int a; byte b; shortint c; int d; } my_struct_s; ​ my_struct_s st {32haaaa_aaaaa, 8hbb, 16hcccc, 32hdddd_dddd}; ​ $display(str%x %x %x %x , st.a, st.b, st.c, st.d); end联合体union联合的这些变量共享一块内存区域具体解读为整形/浮点看我怎么用例 2.39 使用 typedef 创建联合 typedef union { int i; real f; } num_u; num_u un; un.f 0.0; // 把数值设为浮点形式合并结构packed如果不写packedrgb会分别各自占用一个字就造成了大量浪费如果写了packed这三个就会无间隔放在一起一个字就放下了例 2.40 合并结构 typedef struct packed {bit[7:0] r, g, b;} pixel_p_s; pixel_p_s my_pixel;合并or非合并如果是经常对结构整体操作用合并如果经常对成员操作用非合并2.11 类型转换静态转换转换方法是类型(表达式)例 2.41 在整型和实型之间进行静态转换 int i; real r; ​ i int(10.0 - 0.1); // 转换是非强制的 r real(42); // 转换是非强制的动态转换可以用$cast对越界数值进行检查流操作符如果左侧是合并的连续的右侧是不是无所谓。此时意味着把右侧所有元素从左到右排序意味着从右到左排序如果右侧写了byte{j}表示是以单个元素为单位此时元素内部的内容不变只是把元素顺序变了例 2.42 基本的流操作 ​ initial begin int h; bit [7:0] b, g[4], j[4] {8ha, 8hb, 8hc, 8hd}; bit [7:0] q, r, s, t; ​ h { {j}}; // 0a0b0c0d - 把数组打包成整型 h { {j}}; // b030d050 位倒序 h { byte {j}}; // 0d0c0b0a 字节倒序 g { byte {j}}; // 0d,0c,0b,0a 拆分成数组 b { {8b0011_0101}}; // 1010_1100 位倒序 b { 4 {8b0011_0101}}; // 0101_0011 半字节倒序 { {q, r, s, t}} j; // 把 j 分散到四个字节变量里 h { {t, s, r, q}}; // 把字节集中到 h 里 end注意数组下标失配是在数组间进行流操作时常见的错误。数组声明中的下标[256]等同于[0:255]而非[255:0]。由于很多数组使用[highlow]〈由高到低的下标形式进行声明使用流操作把它们的值赋给带(size)下标形式的数组会造成元素倒序。同样如果把声明形式为bit[7:0」src[255:0]的非合并数组使用流操作赋值给声明形式为bit[7:0] [255:0]dst的合并数组则数值的顺序会被打乱。对于合并的字节数组正确的声明形式应该是bti [255:0] [7:0]dst还可以用于结构体和数组间的拆分/打包例 2.44 使用流操作符在结构和数组间进行转换 ​ initial begin typedef struct {int a; byte b; shortint c; int d;} my_struct_s; my_struct_s st {32haaaa_aaaa, 8hbb, 16hcccc, 32hdddd_dddd}; byte b[]; // 将结构转换成字节数组 b { {st}}; // {aa aa aa aa bb cc cc dd dd dd dd} // 将字节数组转换成结构 b {8h11, 8h22, 8h33, 8h44, 8h55, 8h66, 8h77, 8h88, 8h99, 8haa, 8hbb}; st { {b}}; // st 11223344, 55, 6677, 8899aabb end