@@ -1,5 +1,5 @@
---
title : "Josh's Notes: SystemVerilog 验证( Part 2 — 数据类型) "
title : "Josh's Note — SystemVerilog 验证<br> Part 2 数据类型"
mathjax : true
comments : true
copyright : true
@@ -25,12 +25,12 @@ categories:
date : 2022-05-31 10:18:34
---
和 Verilog 相比,SystemVerilog 提供了很多改进的数据结构。虽然其中的部分结构最初是为设计者 创建的,但对测试者 也同样有用。本文将介绍这些对验证很有用的数据结构。
和 Verilog 相比,SystemVerilog 提供了很多改进了 的数据结构。虽然其中的部分结构最初是为设计人员 创建的,但对测试人员 也同样有用。本文将介绍这些对验证很有用的数据结构。
SystemVerilog 引进了一些新的数据类型,它们具有如下优点。
SystemVerilog 引进了一些新的数据类型,它们具有如下优点:
1. **双状态数据类型 ** (*Two-state*): 更好的性能,更低的 内存消耗。
2. **队列、动态数组和关联数组 ** (*Queues, dynamic and associative arrays*): 减少 内存消耗,自带搜索和分类功能。
1. **双状态数据类型 ** (*Two-state*): 性能更好, 内存消耗更低 。
2. **队列、动态数组和关联数组 ** (*Queues, dynamic and associative arrays*):内存消耗减少 ,自带搜索和分类功能。
3. **类和结构 ** (*Classes and structures:*):支持抽象数据结构。
4. **联合和合并结构 ** (*Unions and packed structures*):允许对同一数据有多种视图 (view)。
5. **字符串 ** (*Strings*):支持内建的字符序列。
@@ -44,7 +44,7 @@ Verilog-1995 有两种基本的数据类型:变量 (variable) 和线网 (net)
- 单比特或多比特的无符号数 (unsigned single or multi-bit, `reg [7:0] m` );
- 32 比特的有符号数 (signed 32-bit variable, `integer` );
- 64 比特的无符号数 (unsigned 64-bit variable, `time` )
- 64 比特的无符号数 (unsigned 64-bit variable, `time` );
- 浮点数 (floating point number, `real` )。
若干变量可以被一起存放到定宽数组 (fixed-size array) 里。所有的存储都是静态 (static) 的,意味着所有的变量在整个仿真过程当中都是存活 (alive) 的,子程序 (routine) 不能通过堆栈来保存参数和局部变量。线网可以用来连接设计当中的不同部分,例如门原语和模块实例。尽管线网有很多种用法,但是大多数设计者还是使用标量或矢量 `wire` 来连接各个设计模块的端口。
@@ -55,11 +55,11 @@ SystemVerilog 增加了很多新的数据类型,对设计和验证工程师都
在 Verilog 中,初学者经常分不清 `reg` 和 `wire` 两者的区别。应该使用哪一个来驱动端口?连接不同模块时又该怎么做?SystemVerilog 对经典的 `reg` 数据类型进行了改进,使得它除了作为一个变量以外,还**可以被连续赋值、门单元和模块所驱动**。为了与寄存器类型相区别,这种改进的数据类型称为 `logic` 。任何使用线网的地方均可使用 `logic` ,但**要求 `logic` 不能有多个结构性的驱动**,例如在对双向总线建模的时候。此时,需要使用线网类型,例如 `wire` , SystemVerilog 会对多个数据来源进行解析以后确定最终值。
[例 2. 1 ](#例2. 1 ) 示范了 在 SystemVerilog 中使用 `logic` 类型。
[例 1 ](#例1 ) 给出 在 SystemVerilog 中使用 `logic` 类型的例子 。
<span id="例2. 1"></span >
<a id="例1"></a >
``` verilog 例 2. 1 <code>logic</code> 类型的使用
``` verilog 例 1 <code>logic</code> 类型的使用
module logic_data_type(input logic rst_h);
parameter CYCLE = 20;
logic q, q_l, d, clk, rst_l;
@@ -82,11 +82,11 @@ endmodule
## 1.2. 双状态数据类型
相比四状态数据类型,SystemVerilog 引入的双状态数据类型有利于提高仿真器的性能并减少内存的使用量。最简单的双状态数据类型是 ` bit`,它是无符号的。另四种带符号的双状态数据类型是 ` byte`, ` shortint`, ` int` 和 ` longint`,如[例 2. 2](#例2.2 ) 所示。
相比四状态数据类型,SystemVerilog 引入的双状态数据类型有利于提高仿真器的性能并减少内存的使用量。最简单的双状态数据类型是 ` bit`,它是无符号的。另四种带符号的双状态数据类型是 ` byte`, ` shortint`, ` int` 和 ` longint`,如[例 2](#例2) 所示。
<span id="例2.2 "></span >
<a id="例2"></a >
` `` verilog 例 2.2 带符号的数据类型
` `` verilog 例 2 带符号的数据类型
bit b; // 双状态,单比特
bit [31:0] b32; // 双状态,32 比特无符号整数
int unsigned ui; // 双状态,32 比特无符号整数
@@ -102,11 +102,11 @@ real r; // 双状态,双精度浮点数
> 虽然使用 ` byte` 等数据类型来替代类似 ` logic[7:0]` 进行声明可以让程序更简洁,但需要注意,这些新的数据类型都是**带符号**的,所以 ` byte` 变量的最大值只有 127,而不是 255 (它的范围是 -128~127)。可以使用 ` byte unsigned`,但这其实比使用 ` byte [7:0]` 还要麻烦。在进行随机化时,带符号变量可能会造成意想不到的结果,这一点在后面会讨论到。
> 在把双状态变量连接到 DUT 务必要小心,尤其是连接到 DUT 的输出时,因为 DUT 产生的 ` X` 或 ` Z` 会被转换成双状态值,而测试代码可能永远也察觉不了。这些值被转换成了 ` 0` 还是 ` 1` 并不重要,重要的是要**随时检查未知值的传播**。使用 ` $isunknown()` 操作符,可以在表达式的任意位出现 ` X` 或 ` Z` 时返回 ` 1`,如[例 2. 3](#例2. 3)。
> 在把双状态变量连接到 DUT 务必要小心,尤其是连接到 DUT 的输出时,因为 DUT 产生的 ` X` 或 ` Z` 会被转换成双状态值,而测试代码可能永远也察觉不了。这些值被转换成了 ` 0` 还是 ` 1` 并不重要,重要的是要**随时检查未知值的传播**。使用 ` $isunknown()` 操作符,可以在表达式的任意位出现 ` X` 或 ` Z` 时返回 ` 1`,如[例 3](#例3)。
<span id="例2. 3"></span >
<a id="例3"></a >
` `` verilog 例 2. 3 对四状态值的检查
` `` verilog 例 3 对四状态值的检查
if ($isunknown(iport) == 1)
$display("@%0t: 4-state value detected on iport %b",
$time, iport);
@@ -122,18 +122,18 @@ if ($isunknown(iport) == 1)
Verilog 要求在声明中必须给出数组的上下界。因为几乎所有数组都使用 ` 0` 作为索引下界,所以 SystemVerilog **允许只给出数组宽度的便捷声明方式**,跟 C 语言类似。
<span id="例2. 4"></span >
<a id="例4"></a >
` `` verilog 例 2. 4 定宽数组的声明
` `` verilog 例 4 定宽数组的声明
int lo_hi[0:15]; // 16 个整数 [0]..[15]
int c_style[16]; // 16 个整数 [0]..[15]
` ``
可以通过在变量名后面指定维度的方式来创建**多维定宽数组** (*multidimensional fixed-size array*)。[例 2. 5](#例2. 5) 创建了几个二维的整数数组,大小都是 8 行 4 列,最后一个元素的值被设置为 1。多维数组在 Verilog-2001 中已经引入,但这种紧凑型声明方式却是新的。
可以通过在变量名后面指定维度的方式来创建**多维定宽数组** (*multidimensional fixed-size array*)。[例 5](#例5) 创建了几个二维的整数数组,大小都是 8 行 4 列,最后一个元素的值被设置为 1。多维数组在 Verilog-2001 中已经引入,但这种紧凑型声明方式却是新的。
<span id="例2. 5"></span >
<a id="例5"></a >
` `` verilog 例 2. 5 声明并使用多维数组
` `` verilog 例 5 声明并使用多维数组
int array2 [0:7][0:3]; // 完整的声明
int array3 [8][4]; // 紧凑的声明
@@ -147,28 +147,27 @@ array2[7][3] = 1; // 设置最后一个元素
这适用于所有数组类型,包括定宽数组、动态数组、关联数组和队列,也同时适用于地址中含有 ` X` 或 ` Z` 的情况。线网在没有驱动的时候输出是 ` Z`。
很多 SystemVerilog 仿真器**在存放数组元素时使用 32 比特的字 (word) 边界**,所以 ` byte`, ` shortint` 和 ` int` 都是存放在一个字中,` longint` 存放到两个字中。如[例 2. 6](#例2. 6) 所示,**在非合并数组中,字的低位用来存放数据,高位不使用**。[图 2. 1](#图2. 1) 所示的字节数组 ` b_unpack` 被存放到三个字的空间里。
很多 SystemVerilog 仿真器**在存放数组元素时使用 32 比特的字 (word) 边界**,所以 ` byte`, ` shortint` 和 ` int` 都是存放在一个字中,` longint` 存放到两个字中。如[例 6](#例6) 所示,**在非合并数组中,字的低位用来存放数据,高位不使用**。[图 1](#图1) 所示的字节数组 ` b_unpack` 被存放到三个字的空间里。
<span id="例2. 6"></span >
<a id="例6"></a >
` `` verilog 例 2. 6 非合并数组的声明
` `` verilog 例 6 非合并数组的声明
bit [7:0] b_unpack[3]; // 非合并数组
` ``
<span id="图2. 1"></span >
<div align="center">
<img src=" ../images/post/2022-05-31-josh-systemverilog-2/2022-05-31-josh-systemverilog-2-010-UnpackedArrayStorage.png" width=600px alt="图 2.1 非合并数组的存放"/>
</div>
<a id="图1"></a >
{ width=600px}
仿真器通常使用两个或两个以上连续的字来存放 ` logic` 和 ` integer` 等四状态类型,这**比存放双状态变量多占用一倍的空间**。
## 2.2. 常量数组
[例 2. 7](#例2. 7) 示范了如何使用常量数组,即**用一个单引号加大括号来初始化数组** (注意这里的单引号并不同于编译器指引或宏定义中的单引号),可以一次性地为数组的部分或所有元素赋值。在大括号前标上重复次数可以对多个元素重复赋值,还可以为没有显式赋值的元素指定一个默认值。
[例 7](#例7) 示范了如何使用常量数组,即**用一个单引号加大括号来初始化数组** (注意这里的单引号并不同于编译器指引或宏定义中的单引号),可以一次性地为数组的部分或所有元素赋值。在大括号前标上重复次数可以对多个元素重复赋值,还可以为没有显式赋值的元素指定一个默认值。
<span id="例2. 7"></span >
<a id="例7"></a >
` `` verilog 例 2. 7 初始化一个数组
` `` verilog 例 7 初始化一个数组
int ascend[4] = '{0,1,2,3}; // 对 4 个元素进行初始化
int descend[5];
@@ -180,11 +179,11 @@ descend = '{9, 8, default:1}; // {9,8,1,1,1}
## 2.3. 基本的数组操作—— ` for` 和 ` foreach`
操作数组的最常见的方式是使用 ` for` 或 ` foreach` 循环。在[例 2. 8](#例2. 8) 中,` i` 被声明为 ` for` 循环内的局部变量。SystemVerilog 的 ` $size` 函数返回数组的宽度。在 ` foreach` 循环中,只需要给出数组名并在后面的方括号中给出索引变量,SystemVerilog 便会自动遍历数组中的元素。索引变量将自动声明,并只在循环内有效。
操作数组的最常见的方式是使用 ` for` 或 ` foreach` 循环。在[例 8](#例8) 中,` i` 被声明为 ` for` 循环内的局部变量。SystemVerilog 的 ` $size` 函数返回数组的宽度。在 ` foreach` 循环中,只需要给出数组名并在后面的方括号中给出索引变量,SystemVerilog 便会自动遍历数组中的元素。索引变量将自动声明,并只在循环内有效。
<span id="例2. 8"></span >
<a id="例8"></a >
` `` verilog 例 2. 8 在数组操作中使用 <code>for</code> 和 <code>foreach</code> 循环
` `` verilog 例 8 在数组操作中使用 <code>for</code> 和 <code>foreach</code> 循环
initial begin
bit [31:0] src[5], dst[5];
for (int i=0; i<$size(src); i++)
@@ -194,11 +193,11 @@ initial begin
end
` ``
注意在[例 2. 9](#例2. 9) 中,对多维数组使用 ` foreach` 的语法可能会与设想的有所不同。使用时并不是像 ` [i][j]` 这样把每个下标分别列在不同的方括号里,而是用逗号隔开后放在同一个方括号里,像 ` [i,j]`。
注意在[例 9](#例9) 中,对多维数组使用 ` foreach` 的语法可能会与设想的有所不同。使用时并不是像 ` [i][j]` 这样把每个下标分别列在不同的方括号里,而是用逗号隔开后放在同一个方括号里,像 ` [i,j]`。
<span id="例2. 9"></span >
<a id="例9"></a >
` `` verilog 例 2. 9 初始化并遍历多维数组
` `` verilog 例 9 初始化并遍历多维数组
int md[2][3] = '{'{0,1,2}, '{3,4,5}};
initial begin
@@ -214,11 +213,11 @@ initial begin
end
` ``
[例 2. 9](#例2. 9) 的输出结果如[例 2. 10](#例2. 10) 所示。
[例 9](#例9) 的输出结果如[例 10](#例10) 所示。
<span id="例2. 10"></span >
<a id="例10"></a >
` `` plaintext 例 2. 10 多维数组元素值的打印输出结果
` `` plaintext 例 10 多维数组元素值的打印输出结果
Initial value:
md[0][0] = 0
md[0][1] = 1
@@ -235,11 +234,11 @@ md[1][1] = 5
md[1][2] = 5
` ``
**如果不需要遍历数组中的所有维度,可以在 ` foreach` 循环里忽略不需要的维度**。[例 2. 11](#例2. 11) 把一个二维数组打印成一个方形的阵列。它在外层循环中遍历第一个维度,然后在内层循环中遍历第二个维度。
**如果不需要遍历数组中的所有维度,可以在 ` foreach` 循环里忽略不需要的维度**。[例 11](#例11) 把一个二维数组打印成一个方形的阵列。它在外层循环中遍历第一个维度,然后在内层循环中遍历第二个维度。
<span id="例2. 11"></span >
<a id="例11"></a >
` `` verilog 例 2. 11 打印一个多维数组
` `` verilog 例 11 打印一个多维数组
initial begin
byte twoD[4][6];
foreach(twoD[i,j])
@@ -254,11 +253,11 @@ initial begin
end
` ``
[例 2. 11](#例2. 11) 的输出结果如[例 2. 12](#例2. 12) 所示。
[例 11](#例11) 的输出结果如[例 12](#例12) 所示。
<span id="例2. 12"></span >
<a id="例12"></a >
` `` plaintext 例 2. 12 打印多维数组的输出结果
` `` plaintext 例 12 打印多维数组的输出结果
Initial value:
0: 0 1 2 3 4 5
1: 10 11 12 13 14 15
@@ -273,11 +272,11 @@ Initial value:
## 2.4. 基本的数组操作——复制和比较
可以在不使用循环的情况下对数组进行聚合比较和复制 (aggregate compare and copy, 聚合操作适用于整个数组而不是单个元素),其中比较**只限于等于比较或不等于比较**。[例 2. 13](#例2. 13) 列出了几个比较的例子。操作符 ` ? :` 是一个迷你型的 ` if` 语句,在[例 2. 13](#例2. 13) 中用来对两个字符串进行选择。例子最后的比较语句使用了数组的一部分,` src[1:4]`,它实际上产生了一个有四个元素的临时数组。
可以在不使用循环的情况下对数组进行聚合比较和复制 (aggregate compare and copy, 聚合操作适用于整个数组而不是单个元素),其中比较**只限于等于比较或不等于比较**。[例 13](#例13) 列出了几个比较的例子。操作符 ` ? :` 是一个迷你型的 ` if` 语句,在[例 13](#例13) 中用来对两个字符串进行选择。例子最后的比较语句使用了数组的一部分,` src[1:4]`,它实际上产生了一个有四个元素的临时数组。
<span id="例2. 13"></span >
<a id="例13"></a >
` `` verilog 例 2. 13 数组的复制和比较操作
` `` verilog 例 13 数组的复制和比较操作
initial begin
bit [31:0] src[5] = '{0,1,2,3,4},
dst[5] = '{5,4,3,2,1};
@@ -307,11 +306,11 @@ end
## 2.5. 同时使用位下标和数组下标
在 Verilog-1995 中一个很不方便的地方就是数组下标 (array subscript) 和位下标 (bit subscript) 不能同时使用。Verilog-2001 对定宽数组取消了这个限制。[例 2. 14](#例2. 14) 打印出数组的第一个元素 (二进制 101 )、它的最低位 (1) 以及紧接的高两位 (二进制 10)。
在 Verilog-1995 中一个很不方便的地方就是数组下标 (array subscript) 和位下标 (bit subscript) 不能同时使用。Verilog-2001 对定宽数组取消了这个限制。[例 14](#例14) 打印出数组的第一个元素 (二进制 101 )、它的最低位 (1) 以及紧接的高两位 (二进制 10)。
<span id="例2. 14"></span >
<a id="例14"></a >
` `` verilog 例 2. 14 同时使用数组下标和位下标
` `` verilog 例 14 同时使用数组下标和位下标
initial begin
bit [31:0] src[5] = '{5{5}};
$displayb(src[0],, // 'b101 或 'd5
@@ -328,11 +327,11 @@ end
## 2.7. 合并数组的例子
声明合并数组时,**合并的位和数组大小是数据类型的一部分,必须在变量名前面指定**。数组大小定义的格式必须是 ` [msb:lsb]`,而不是 ` [size]`。[例 2. 15](#例2. 15) 中的变量 ` bytes` 是一个有 4 个字节的合并数组,使用单独的 32 比特的字来存放,如图 2.2 所示。
声明合并数组时,**合并的位和数组大小是数据类型的一部分,必须在变量名前面指定**。数组大小定义的格式必须是 ` [msb:lsb]`,而不是 ` [size]`。[例 15](#例15) 中的变量 ` bytes` 是一个有 4 个字节的合并数组,使用单独的 32 比特的字来存放,如图 2 所示。
<span id="例2. 15"></span >
<a id="例15"></a >
` `` verilog 例 2. 15 合并数组的声明和用法
` `` verilog 例 15 合并数组的声明和用法
bit [3:0] [7:0] bytes; // 4 个 字节 组装成 32 比特
bytes = 32'hCafe_Dada;
$displayh(bytes,, // 显示所有的 32 比特
@@ -340,16 +339,15 @@ $displayh(bytes,, // 显示所有的 32 比特
bytes[3][7]); // 显示 MSB “1”
` ``
<span id="图2.2 "></span >
<div align="center">
<img src="../images/post/2022-05-31-josh-systemverilog-2/2022-05-31-josh-systemverilog-2-020-PackedArrayLayout.png" width=600px alt="图 2.2 合井数组存放示意图"/>
</div>
<a id="图2"></a >
合并和非合并数组可以混合使用。可以使用数组来表示存储单元,这些单元可以按比特、字节或长字的方式进行存取。在[例 2.16](#例2.16) 中,` barray` 是一个具有 3 个合并元素的非合并数组。
{width=600px}
<span id="例2.16"></span>
合并和非合并数组可以混合使用。可以使用数组来表示存储单元,这些单元可以按比特、字节或长字的方式进行存取。在[例 16](#例16) 中,` barray` 是一个具有 3 个合并元素的非合并数组。
` `` verilog 例 2.16 合并数组的声明和用法
<a id="例16"></a>
` `` verilog 例 16 合并数组的声明和用法
bit [3:0] [7:0] barray [3]; // 合并,3× 32 比特
bit [31:0] lw = 32'h0123_4567; // 字
bit [7:0] [3:0] nibbles; // 合并数组
@@ -359,12 +357,11 @@ barray[0][1][6] = 1'b1;
nibbles = barray[0]; // 复制合并数组的元素值
` ``
[例 2. 15](#例2. 15) 中的变量 ` bytes` 是一个具有 4 个字节的合并数组,以单字形式存放。` barray` 则是一个具有 3 个类似 ` bytes` 元素的数组,其存放形式如图 2. 3。
[例 15](#例15) 中的变量 ` bytes` 是一个具有 4 个字节的合并数组,以单字形式存放。` barray` 则是一个具有 3 个类似 ` bytes` 元素的数组,其存放形式如图 3。
<span id="图2. 3"></span >
<div align="center">
<img src=" ../images/post/2022-05-31-josh-systemverilog-2/2022-05-31-josh-systemverilog-2-030-PackedArrayBitLayout.png" width=600px alt="图 2.3 合并数组存放示意图"/>
</div>
<a id="图3"></a >
{ width=600px}
- 使用一个下标,可以得到一个字的数据,如 ` barray[2]`;
- 使用两个下标,可以得到一个字节的数据,如 ` barray[0][3]`;
@@ -372,13 +369,13 @@ nibbles = barray[0]; // 复制合并数组的元素值
注意数组声明中在变量名后面指定了数组的大小,` barray[3]`,这个维度是非合并的,所以在使用该数组时至少要有一个下标。
[例 2. 16](#例2. 16) 中的最后一行在两个合并数组间实现复制。由于操作是以比特为单位进行的,所以即使数组维度不同也可以进行复制。
[例 16](#例16) 中的最后一行在两个合并数组间实现复制。由于操作是以比特为单位进行的,所以即使数组维度不同也可以进行复制。
## 2.8. 合井数组和非合并数组的选择
应该选择合并数组还是非合并数组呢?当需要和标量进行相互转换时,使用合并数组会非常方便。例如,以字节或字为单位对存储单元进行操作。[图 2. 3](#图2. 3) 中所示的 ` barray` 可以满足这一要求。任何数组类型都可以合并,包括动态数组、队列和关联数组。
应该选择合并数组还是非合并数组呢?当需要和标量进行相互转换时,使用合并数组会非常方便。例如,以字节或字为单位对存储单元进行操作。[图 3](#图3) 中所示的 ` barray` 可以满足这一要求。任何数组类型都可以合并,包括动态数组、队列和关联数组。
**如果需要等待数组中的变化,则必须使用合并数组**。当 testbench 需要通过存储器数据的变化来唤醒时,需要使用 ` @` 操作符,但 **` @` 操作符只能用于标量或者合并数组**。在[例 2. 16](#例2. 16) 中,可以把 ` lw` 和 ` barray[0]` 用作敏感信号,但却不能用整个的 ` barray` 数组,除非把它扩展成:` @(barray[0] or barray[1] or barray[2])`。
**如果需要等待数组中的变化,则必须使用合并数组**。当 testbench 需要通过存储器数据的变化来唤醒时,需要使用 ` @` 操作符,但 **` @` 操作符只能用于标量或者合并数组**。在[例 16](#例16) 中,可以把 ` lw` 和 ` barray[0]` 用作敏感信号,但却不能用整个的 ` barray` 数组,除非把它扩展成:` @(barray[0] or barray[1] or barray[2])`。
# 3. 动态数组
@@ -386,11 +383,11 @@ nibbles = barray[0]; // 复制合并数组的元素值
SystemVerilog 提供了动态数组类型,可以**在仿真时分配空间或调整宽度**,这样在仿真中就可以使用最小的存储星。
动态数组在声明时使用空的下标 ` []`,表示数组的宽度不在编译时给出,在程序运行时再指定。数组在最开始时是空的,所以必须调用 ` new[]` 操作符来分配空间,同时在方括号中传递数组宽度。可以把数组名传给 ` new[]` 构造符,并把已有数组的值复制到新数组里,如[例 2. 17](#例2. 17) 所示。
动态数组在声明时使用空的下标 ` []`,表示数组的宽度不在编译时给出,在程序运行时再指定。数组在最开始时是空的,所以必须调用 ` new[]` 操作符来分配空间,同时在方括号中传递数组宽度。可以把数组名传给 ` new[]` 构造符,并把已有数组的值复制到新数组里,如[例 17](#例17) 所示。
<span id="例2. 17"></span >
<a id="例17"></a >
` `` verilog 例 2. 17 使用动态数组
` `` verilog 例 17 使用动态数组
int dyn[], d2[]; // 声明动态数组
initial begin
@@ -406,7 +403,7 @@ initial begin
end
` ``
在[例 2. 17](#例2. 17) 中:
在[例 17](#例17) 中:
- A:调用 ` new[5]` 为动态数组 ` dyn` 分配了 5 个整型元素。
- B:把数组的索引值赋给相应的元素。
@@ -418,11 +415,11 @@ end
调用系统函数 ` $size` 可以返回数组的宽度。动态数组有一些内建的子程序 (routines),例如 ` delete` 和 ` size`。
如果想声明一个常数数组但又不想统计元素的个数,可以使用动态数组并使用常量数组进行赋值。在[例 2. 18](#例2. 18) 中声明的 ` mask` 数组具有 9 个 8 比特元素,SystemVerilog 会自动统计元素的个数,这比声明一个定宽数组然后不小心把宽度错指为 8 要好。
如果想声明一个常数数组但又不想统计元素的个数,可以使用动态数组并使用常量数组进行赋值。在[例 18](#例18) 中声明的 ` mask` 数组具有 9 个 8 比特元素,SystemVerilog 会自动统计元素的个数,这比声明一个定宽数组然后不小心把宽度错指为 8 要好。
<span id="例2. 18"></span >
<a id="例18"></a >
` `` verilog 例 2. 18 使用动态数组来保存元素数量不定的列表
` `` verilog 例 18 使用动态数组来保存元素数量不定的列表
bit [7:0] mask[] = '{8'b0000_0000, 8'b0000_0001,
8'b0000_0011, 8'b0000_0111,
8'b0000_1111, 8'b0001_1111,
@@ -438,13 +435,13 @@ bit [7:0] mask[] = '{8'b0000_0000, 8'b0000_0001,
SystemVerilog 引进了一种新的数据类型——队列 (queue),它结合了链表和数组的优点。队列与链表相似,**可以在一个队列中的任何地方增加或删除元素**,这类操作在性能上的损失比动态数组小得多,因为动态数组需要分配新的数组并复制所有元素的值。队列与数组相似,可以通过索引实现对任一元素的访问,而不需要像链表那样去遍历目标元素之前的所有元素。
队列的声明是使用带有美元符号的下标:` [$]`。队列元素的编号从 ` 0` 到 ` $`。[例 2. 19](#例2. 19) 示范了如何使用方法 (method) 在队列中增加和删除元素。注意队列的常量 (literal) 只有大括号而没有数组常量中开头的单引号。
队列的声明是使用带有美元符号的下标:` [$]`。队列元素的编号从 ` 0` 到 ` $`。[例 19](#例19) 示范了如何使用方法 (method) 在队列中增加和删除元素。注意队列的常量 (literal) 只有大括号而没有数组常量中开头的单引号。
SystemVerilog 的队列类似于标准模板库 (standard template library) 中的双端队列。通过增加元素来创建队列,SystemVerilog 会分配额外的空间以便新元素能够被快速插入。当元素增加到超过原有空间的容量时,SystemVerilog 会自动分配更多的空间。因此队列可以扩大或缩小,但不用像动态数组那样在性能上付出很大代价,SystemVerilog 会随时记录闲置的空间。注意不要对队列使用构造函数 ` new[]`。
<span id="例2. 19"></span >
<a id="例19"></a >
` `` verilog 例 2. 19 队列的操作
` `` verilog 例 19 队列的操作
int j = 1,
q2[$] = {3,4}, // 队列的常量不需要使用 “'”
q[$] = {0,2,5}; // {0,2,5}
@@ -465,11 +462,11 @@ initial begin
end
` ``
还可以使用字下标串联来替代方法。如果把 ` $` 放在一个范围表达式的左边,那么 ` $` 将代表最小值,例如 ` [$:2]` 就代表 ` [0:2]`。如果 ` $` 放在表达式的右边,则代表最大值,如[例 2. 20](#例2. 20) 中初始化块的第一行里的 ` [1:$]` 就表示 ` [1:2]`。
还可以使用字下标串联来替代方法。如果把 ` $` 放在一个范围表达式的左边,那么 ` $` 将代表最小值,例如 ` [$:2]` 就代表 ` [0:2]`。如果 ` $` 放在表达式的右边,则代表最大值,如[例 20](#例20) 中初始化块的第一行里的 ` [1:$]` 就表示 ` [1:2]`。
<span id="例2. 20"></span >
<a id="例20"></a >
` `` verilog 例 2. 20 队列的操作
` `` verilog 例 20 队列的操作
int j = 1,
q2[$] = {3,4}, // 队列的常量不需要使用 “'”
q[$] = {0,2,5}; // {0,2,5}
@@ -499,22 +496,21 @@ end
如果只是偶尔需要创建一个大容量的数组,那么动态数组已经足够好用了,但是如果需要超大容量的呢?假设正在对一个有着几个 G 字节寻址范围的处理器进行建模。在典型的测试中,这个处理器可能只访问了用来存放可执行代码和数据的几百或几千个字节,这种情况下对几 G 字节的存储空间进行分配和初始化显然是浪费的。
SystemVerilog 提供了关联数组 (associative array) 类型,用来保存稀疏矩阵 (sparse matrix) 的元素。这意味着当对一个非常大的地址空间进行寻址时,SystemVerilog 只为实际写入的元素分配空间。在[图 2. 4](#图2. 4) 中,关联数组只保留 0…3、42、1000、4521 和 200,000 等位置上的值。这样,用来存放这些值的空间比有 200,000 个条目的定宽或动态数组所占用的空间要小得多。
SystemVerilog 提供了关联数组 (associative array) 类型,用来保存稀疏矩阵 (sparse matrix) 的元素。这意味着当对一个非常大的地址空间进行寻址时,SystemVerilog 只为实际写入的元素分配空间。在[图 4](#图4) 中,关联数组只保留 0…3、42、1000、4521 和 200,000 等位置上的值。这样,用来存放这些值的空间比有 200,000 个条目的定宽或动态数组所占用的空间要小得多。
<span id="图2. 4"></span >
<div align="center">
<img src=" ../images/post/2022-05-31-josh-systemverilog-2/2022-05-31-josh-systemverilog-2-040-AssociativeArray.png" width=600px alt="图 2.4 关联数组"/>
</div>
<a id="图4"></a >
{ width=600px}
仿真器可以采用树或哈希表的形式来存放关联数组,但有一定的额外开销。当保存索引值比较分散的数组时,例如使用 32 位地址或 64 位数据作为索引的数据包,这种额外开销显然是可以接受的。
关联数组采用**在方括号中放置数据类型[^DataTypeNote]**的形式来进行声明,例如 ` [int]` 或 ` [Packet]`。[例 2. 21](#例2. 21) 示范了关联数组的声明、初始化和遍历过程。
关联数组采用**在方括号中放置数据类型[^DataTypeNote]**的形式来进行声明,例如 ` [int]` 或 ` [Packet]`。[例 21](#例21) 示范了关联数组的声明、初始化和遍历过程。
[^DataTypeNote]: 也可以采用通配符作为下标进行关联数组的声明,例如 ` wild[*]`。但是不推荐使用这种风格,因为不明确指明数据类型会导致很多问题。一个典型的问题是在 ` foreach` 循环中——在 ` foreach(wild[j])` 中的变量 ` j` 究竟是什么类型?
<span id="例2. 21"></span >
<a id="例21"></a >
` `` verilog 例 2. 21 关联数组的声明、初始化和使用
` `` verilog 例 21 关联数组的声明、初始化和使用
initial begin
bit [63:0] assoc[int], idx = 1;
@@ -543,13 +539,13 @@ initial begin
end
` ``
[例 2. 21](#例2. 21) 中的关联数组 ` assoc` 具有稀疏分布的元素:1、2、4、8、16 等等。简单的 ` for` 循环并不能遍历该数组,需要使用 ` foreach` 循环来完成。如果想控制得更好,可以在 ` do ... while` 循环中使用 ` first` 和 ` next` 函数。这些函数可以修改索引参数的值,然后根据数组是否为空返回 0 或 1。
[例 21](#例21) 中的关联数组 ` assoc` 具有稀疏分布的元素:1、2、4、8、16 等等。简单的 ` for` 循环并不能遍历该数组,需要使用 ` foreach` 循环来完成。如果想控制得更好,可以在 ` do ... while` 循环中使用 ` first` 和 ` next` 函数。这些函数可以修改索引参数的值,然后根据数组是否为空返回 0 或 1。
和 Perl 语言中的哈希数组类似,关联数组也可以使用字符串索引进行寻址。[例 2. 22](#例2. 22) 使用字符串索引读取文件,并建立关联数组 ` switch`,以实现从字符串到数字的映射。如[例 2. 22](#例2. 22) 所示,可以使用函数 ` exists()` 来检查元素是否存在。如果试图读取尚未被写入的元素,SystemVerilog 会返回数组类型的默认值,例如对于双状态类型是 ` 0`,对于四状态类型是 ` X`。
和 Perl 语言中的哈希数组类似,关联数组也可以使用字符串索引进行寻址。[例 22](#例22) 使用字符串索引读取文件,并建立关联数组 ` switch`,以实现从字符串到数字的映射。如[例 22](#例22) 所示,可以使用函数 ` exists()` 来检查元素是否存在。如果试图读取尚未被写入的元素,SystemVerilog 会返回数组类型的默认值,例如对于双状态类型是 ` 0`,对于四状态类型是 ` X`。
<span id="例2. 22"></span >
<a id="例22"></a >
` `` verilog 例 2. 22 使用带字符串索引的关联数组
` `` verilog 例 22 使用带字符串索引的关联数组
/*
输入文件的内容如下:
42 min_address
@@ -594,11 +590,11 @@ SystemVerilog 提供了很多数组的方法,可用于任何一种非合并的
## 7.1. 数组缩减方法
基本的数组缩减方法是把一个数组缩减成一个值,如[例 2. 23](例2. 23) 所示。最常用的缩减方法是 ` sum`,它对数组中的所有元素求和。这里必须对 SystemVerilog 处理操作**位宽**的规则十分小心。默认情况下,如果把一个单比特数组的所有元素相加,其和也是单比特的。但如果使用 32 比特的表达式,把结果保存在 32 比特的变量里,与一个 32 比特的变量进行比较,或者使用适当的 ` with` 表达式,SystemVerilog 都会在数组求和的过程中使用 32 比特位宽。
基本的数组缩减方法是把一个数组缩减成一个值,如[例 23](例23) 所示。最常用的缩减方法是 ` sum`,它对数组中的所有元素求和。这里必须对 SystemVerilog 处理操作**位宽**的规则十分小心。默认情况下,如果把一个单比特数组的所有元素相加,其和也是单比特的。但如果使用 32 比特的表达式,把结果保存在 32 比特的变量里,与一个 32 比特的变量进行比较,或者使用适当的 ` with` 表达式,SystemVerilog 都会在数组求和的过程中使用 32 比特位宽。
<span id="例2. 23"></span >
<a id="例23"></a >
` `` verilog 例 2. 23 数组求和
` `` verilog 例 23 数组求和
bit on[10]; // 单比特数组
int total;
@@ -627,11 +623,11 @@ end
其他的数组缩减方法还有 ` product` (积), ` and` (与), ` or` (或) 和 ` xor` (异或)。SystemVerilog 没有提供专门从数组里随机选取一个元素的方法。所以对于定宽数组队列、动态数组和关联数组可以使用 ` $urandom_range($size (array)-1)`,而对于队列和动态数组还可以使用 ` $urandom_ range(array.size()-1)`。
如果想从一个关联数组中随机选取一个元素,则需要逐个访问它之前的元素,因为无法直接访问到第 $N$ 个元素。[例 2. 24](#例2. 24) 示范了如何从一个以整数值作为索引的关联数组中随机选取一个元素。如果数组是以字符串作为索引,只需要将 ` idx` 的类型改为 ` string` 即可。
如果想从一个关联数组中随机选取一个元素,则需要逐个访问它之前的元素,因为无法直接访问到第 $N$ 个元素。[例 24](#例24) 示范了如何从一个以整数值作为索引的关联数组中随机选取一个元素。如果数组是以字符串作为索引,只需要将 ` idx` 的类型改为 ` string` 即可。
<span id="例2. 24"></span >
<a id="例24"></a >
` `` verilog 例 2. 24 从一个关联数组中随机选取一个元素
` `` verilog 例 24 从一个关联数组中随机选取一个元素
int aa[int], rand_idx, element, count;
element = $urandom_range(aa.size()-1);
@@ -649,11 +645,11 @@ $display("%0d element aa[%0d] = %0d",
数组中的最大值是什么?数组中有没有包含某个特定值?要想在非合并数组中查找数据,可以使用数组定位方法。**这些方法的返回值通常是一个队列**。
[例 2. 25](#例2. 25) 使用一个定宽数组 ` f[6]`,一个动态数组 ` d[]` 和一个队列 ` q[$]`。` min` 和 ` max` 函数能够找出数组中的最小值和最大值。注意,它们返回的是一个队列而非标量。这些方法也适用于关联数组。方法 ` unique` 返回的是在数组中具有唯一值的队列,即排除掉重复的数值。
[例 25](#例25) 使用一个定宽数组 ` f[6]`,一个动态数组 ` d[]` 和一个队列 ` q[$]`。` min` 和 ` max` 函数能够找出数组中的最小值和最大值。注意,它们返回的是一个队列而非标量。这些方法也适用于关联数组。方法 ` unique` 返回的是在数组中具有唯一值的队列,即排除掉重复的数值。
<span id="例2. 25"></span >
<a id="例25"></a >
` `` verilog 例 2. 25 数组定位方法:<code>min</code>、<code>max</code>、<code>unique</code>
` `` verilog 例 25 数组定位方法:<code>min</code>、<code>max</code>、<code>unique</code>
int aa[int], rand_idx, element, count;
element = $urandom_range(aa.size()-1);
@@ -667,11 +663,11 @@ $display("%0d element aa[%0d] = %0d",
element, rand_idx, aa[rand_idx]);
` ``
使用 ` foreach` 循环固然可以实现数组的完全搜索,但是如果使用 SystemVerilog 的定位方法,则只需一个操作便可完成。表达式 ` with` 可以指示 SystemVerilog 如何进行搜索,如[例 2. 26](#例2. 26) 所示。
使用 ` foreach` 循环固然可以实现数组的完全搜索,但是如果使用 SystemVerilog 的定位方法,则只需一个操作便可完成。表达式 ` with` 可以指示 SystemVerilog 如何进行搜索,如[例 26](#例26) 所示。
<span id="例2. 26"></span >
<a id="例26"></a >
` `` verilog 例 2. 26 数组定位方法:<code>find</code>
` `` verilog 例 26 数组定位方法:<code>find</code>
int d[] = '{9,1,8,3,4,4}, tq[$];
// 找出所有大于 3 的元素
@@ -689,22 +685,22 @@ tq = d.find_last with (item==4); // {4}
tq = d.find_last_index with (item==4); // {5} d[5]=4
` ``
在条件语句 ` with` 中,` item` 被称为重复参数,它代表了数组中一个单独的元素。` item` 是默认的名字,也可以指定别的名字,只要在数组方法的参数列表中列出来就可以了,如[例 2. 27](#例2. 27) 所示。
在条件语句 ` with` 中,` item` 被称为重复参数,它代表了数组中一个单独的元素。` item` 是默认的名字,也可以指定别的名字,只要在数组方法的参数列表中列出来就可以了,如[例 27](#例27) 所示。
<span id="例2. 27"></span >
<a id="例27"></a >
` `` verilog 例 2. 27 重复参数的声明
` `` verilog 例 27 重复参数的声明
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);
` ``
[例 2. 28](#例2. 28) 示范了几种对数组子集进行求和的方式。第一次求和 (` total`) 是先把元素值与 7 进行比较,比较表达式返回 1 (为真) 或 0 (为假),然后再把返回结果与对应元素相乘,所以 ` {9, 0, 8, 0, 0, 0}` 的元素和是 17。第二次求和 (` total`) 则使用条件操作符 ` ? :` 进行计算。
[例 28](#例28) 示范了几种对数组子集进行求和的方式。第一次求和 (` total`) 是先把元素值与 7 进行比较,比较表达式返回 1 (为真) 或 0 (为假),然后再把返回结果与对应元素相乘,所以 ` {9, 0, 8, 0, 0, 0}` 的元素和是 17。第二次求和 (` total`) 则使用条件操作符 ` ? :` 进行计算。
<span id="例2. 28"></span >
<a id="例28"></a >
` `` verilog 例 2. 28 数组定位方法
` `` verilog 例 28 数组定位方法
int count, total, d[] = '{9,1,8,3,4,4};
count = d.sum with (item > 7); // 2: {9, 8}
@@ -714,17 +710,17 @@ total = d.sum with (item < 8 ? item : 0); // 12=1+3+4+4
count = d.sum with (item == 4); // 2: {4, 4}
` ``
当把数组缩减方法与条件语句 ` with` 结合使用时,会产生令人惊讶的结果,例如 ` sum` 方法。在[例 2. 28](#例2. 28) 中,` sum` 操作符的结果是条件表达式为真的次数。对于[例 2. 28](#例2. 28) 的第一个运算语句来说,总共有两个数组元素大于 7 (9 和 8),所以 ` count` 最后得 2。
当把数组缩减方法与条件语句 ` with` 结合使用时,会产生令人惊讶的结果,例如 ` sum` 方法。在[例 28](#例28) 中,` sum` 操作符的结果是条件表达式为真的次数。对于[例 28](#例28) 的第一个运算语句来说,总共有两个数组元素大于 7 (9 和 8),所以 ` count` 最后得 2。
> 返回值为索引的数组定位方法,其返回的队列类型是 ` int` 而非 ` integer`,例如 ` find_index` 方法。如果在这些语句中不小心用错了数据类型,那么代码有可能通不过编译。
## 7.3. 数组的排序
SystemVerilog 有几个可以改变数组中元素顺序的方法。可以对元素进行正排序、逆排序,或是打乱它们的顺序,如[例 2. 29](#例2. 29) 所示。注意,与前述数组定位方法不同的是,**排序方法改变了原始数组**,而**数组定位方法新建了一个队列来保存结果**。
SystemVerilog 有几个可以改变数组中元素顺序的方法。可以对元素进行正排序、逆排序,或是打乱它们的顺序,如[例 29](#例29) 所示。注意,与前述数组定位方法不同的是,**排序方法改变了原始数组**,而**数组定位方法新建了一个队列来保存结果**。
<span id="例2. 29"></span >
<a id="例29"></a >
` `` verilog 例 2. 29 对数组排序
` `` verilog 例 29 对数组排序
int d[] = '{9,1,8,3,4,4};
d.reverse(); // '{4,4,3,8,1,9}
d.sort(); // '{1,3,4,4,8,9}
@@ -732,11 +728,11 @@ d.rsort(); // '{9,8,4,4,3,1}
d.shuffle(); // '{9,4,3,8,1,4}
` ``
` reverse` 和 ` shuffle` 方法不能带 ` with` 条件语句,**它们的作用范围是整个数组**。[例 2. 30](#例2. 30) 示范了如何使用子域对一个结构进行排序。结构和合并结构后面会有解释。
` reverse` 和 ` shuffle` 方法不能带 ` with` 条件语句,**它们的作用范围是整个数组**。[例 30](#例30) 示范了如何使用子域对一个结构进行排序。结构和合并结构后面会有解释。
<span id="例2. 30"></span >
<a id="例30"></a >
` `` verilog 例 2. 30 对结构数组进行排序
` `` verilog 例 30 对结构数组进行排序
struct packed { byte red, green, blue; } c[];
initial begin
c = new[100]; // 分配 100 个像素
@@ -752,11 +748,11 @@ end
## 7.4. 使用数组定位方法建立记分板
数组定位方法可以用来建立记分板。[例 2. 31](#例2. 31) 定义了包结构 (` Packet`),然后建立了一个由包结构队列组成的记分板。后面会解释如何使用 ` typedef` 创建结构。
数组定位方法可以用来建立记分板。[例 31](#例31) 定义了包结构 (` Packet`),然后建立了一个由包结构队列组成的记分板。后面会解释如何使用 ` typedef` 创建结构。
<span id="例2. 31"></span >
<a id="例31"></a >
` `` verilog 例 2. 31 带数组方法的记分板
` `` verilog 例 31 带数组方法的记分板
typedef struct packed
{bit [7:0] addr;
bit [7:0] pr;
@@ -777,7 +773,7 @@ function void check_addr(bit [7:0] addr);
endfunction : check_addr
` ``
[例 2. 31](#例2. 31) 中的 ` check_addr()` 函数在记分板里寻找和参数匹配的地址。` find_index()` 方法返回一个 ` int` 队列。如果该队列为空 (` size==0`),则说明没有匹配值。如果该队列有一个成员 (` size==1`),则说明有一个匹配,该匹配元素随后被 ` check_addr()` 函数删除掉。如果该队列有多个成员 (` size>1`),则说明记分板里有多个包地址和目标值匹配。
[例 31](#例31) 中的 ` check_addr()` 函数在记分板里寻找和参数匹配的地址。` find_index()` 方法返回一个 ` int` 队列。如果该队列为空 (` size==0`),则说明没有匹配值。如果该队列有一个成员 (` size==1`),则说明有一个匹配,该匹配元素随后被 ` check_addr()` 函数删除掉。如果该队列有多个成员 (` size>1`),则说明记分板里有多个包地址和目标值匹配。
对于包信息的存储,更好的方式是采用类 (class),后续会有相关介绍。
@@ -843,11 +839,11 @@ endfunction : check_addr
# 9. 使用 ` typedef` 创建新的类型
typedef 语句可以用来创建新的类型。例如,要求一个算术逻辑单元 (ALU) 在编译时可配置,以适应 8 比特、 16 比特、 24 比特或 32 比特等不同位宽的操作数。在 Verilog 中,可以为操作数的位宽和类型分别定义一个宏 (macro),如[例 2. 32](#例2. 32) 所示。
typedef 语句可以用来创建新的类型。例如,要求一个算术逻辑单元 (ALU) 在编译时可配置,以适应 8 比特、 16 比特、 24 比特或 32 比特等不同位宽的操作数。在 Verilog 中,可以为操作数的位宽和类型分别定义一个宏 (macro),如[例 32](#例32) 所示。
<span id="例2. 32"></span >
<a id="例32"></a >
` `` verilog 例 2. 32 Verilog 中用户自定义的类型宏
` `` verilog 例 32 Verilog 中用户自定义的类型宏
// 旧的 Verilog 风格
` define OPSIZE 8
`define OPREG reg [` OPSIZE-1:0]
@@ -857,9 +853,9 @@ typedef 语句可以用来创建新的类型。例如,要求一个算术逻辑
这种情况并没有创建新的类型,而只是在进行文本替换。在 SystemVerilog 中,采用下面的代码可以创建新的类型。本文约定,所有用户自定义类型都带后缀 “` _t`”。
<span id="例2. 33"></span >
<a id="例33"></a >
` `` verilog 例 2. 33 SystemVerilog 中用户自定义类型
` `` verilog 例 33 SystemVerilog 中用户自定义类型
// 新的 SystemVerilog 风格
parameter OPSIZE = 8;
typedef reg [OPSIZE-1:0] opreg_t;
@@ -873,18 +869,18 @@ opreg_t op_a, op_b;
> 用户自定义的最有用的类型是双状态的 32 比特的无符号整数。在 testbench 中,很多数值都是正整数,例如字段长度或事件次数,这种情况下如果定义有符号整数就会出问题。把对 ` uint` 的定义放到通用定义程序包中,这样就可以在仿真程序的任何地方使用它。
<span id="例2. 34"></span >
<a id="例34"></a >
` `` verilog 例 2. 34 <code>uint</code> 的定义
` `` verilog 例 34 <code>uint</code> 的定义
typedef bit [31:0] uint; // 32 比特双状态无符号数
typedef int unsigned uint; // 等效定义
` ``
对新的数组类型的定义并不是很明显,需要把数组的下标放在新的数组名称中。[例 2. 35](#例2. 35) 创建了一种新的类型,` fixed_array5`,它是一个有着 5 个元素的定宽数组。[例 2. 35](#例2. 35) 接着声明了一个这种类型的数组并进行了初始化。
对新的数组类型的定义并不是很明显,需要把数组的下标放在新的数组名称中。[例 35](#例35) 创建了一种新的类型,` fixed_array5`,它是一个有着 5 个元素的定宽数组。[例 35](#例35) 接着声明了一个这种类型的数组并进行了初始化。
<span id="例2. 35"></span >
<a id="例35"></a >
` `` verilog 例 2. 35 用户自定义数组类型
` `` verilog 例 35 用户自定义数组类型
typedef int fixed_array5[5];
fixed_array5 f5;
@@ -902,19 +898,19 @@ Verilog 的最大缺陷之一是没有数据结构。在 SystemVerilog 中可以
## 10.1. 使用 ` struct` 创建新类型
可以把若干变量组合到一个结构中。[例 2. 36](#例2. 36) 创建了一个名为 ` pixel` 的结构,它有三个无符号的字节变量,分别代表红、绿和蓝。
可以把若干变量组合到一个结构中。[例 36](#例36) 创建了一个名为 ` pixel` 的结构,它有三个无符号的字节变量,分别代表红、绿和蓝。
<span id="例2. 36"></span >
<a id="例36"></a >
` `` verilog 例 2. 36 创建一个 <code>pixel</code> 类型
` `` verilog 例 36 创建一个 <code>pixel</code> 类型
struct {bit [7:0] r, g, b;} pixel;
` ``
[例 2. 36](#例2. 36) 中的声明只是创建了一个 ` pixel` 变量。要想在端口和程序中共享它,则必须创建一个新的类型,如[例 2. 37](#例2. 37) 所示。
[例 36](#例36) 中的声明只是创建了一个 ` pixel` 变量。要想在端口和程序中共享它,则必须创建一个新的类型,如[例 37](#例37) 所示。
<span id="例2. 37"></span >
<a id="例37"></a >
` `` verilog 例 2. 37 <code>pixel</code> 结构
` `` verilog 例 37 <code>pixel</code> 结构
typedef struct {bit [7:0] r, g, b;} pixel_s;
pixel_s my_pixel;
` ``
@@ -923,11 +919,11 @@ pixel_s my_pixel;
## 10.2. 对结构进行初始化
可以在声明或者过程赋值语句中把多个值赋给一个结构体,就像数组那样。如[例 2. 38](#例2. 38) 所示,赋值时要把数值放到带单引号的大括号中。
可以在声明或者过程赋值语句中把多个值赋给一个结构体,就像数组那样。如[例 38](#例38) 所示,赋值时要把数值放到带单引号的大括号中。
<span id="例2. 38"></span >
<a id="例38"></a >
` `` verilog 例 2. 38 对 <code>struct</code> 类型进行初始化
` `` verilog 例 38 对 <code>struct</code> 类型进行初始化
initial begin
typedef struct {int a;
byte b;
@@ -944,11 +940,11 @@ end
## 10.3. 创建可容纳不同类型的联合
在硬件中,寄存器里的某些位的含义可能与其他位的值有关。例如,不同的操作码对应的处理器指令格式也不同。带立即操作数的指令,它在操作数位置上存放的是一个常量。整数指令对这个立即数的译码结果会与浮点指令大不相同。[例 2. 39](#例2. 39) 把整数 ` i` 和实数 ` f` 存放在同一位置上。
在硬件中,寄存器里的某些位的含义可能与其他位的值有关。例如,不同的操作码对应的处理器指令格式也不同。带立即操作数的指令,它在操作数位置上存放的是一个常量。整数指令对这个立即数的译码结果会与浮点指令大不相同。[例 39](#例39) 把整数 ` i` 和实数 ` f` 存放在同一位置上。
<span id="例2. 39"></span >
<a id="例39"></a >
` `` verilog 例 2. 39 使用一个下标 <code>typedef</code> 创建联合
` `` verilog 例 39 使用一个下标 <code>typedef</code> 创建联合
typedef union { int i; real f; } num_u;
num_u un;
un.f = 0.0; // 把数值设为浮点形式
@@ -960,11 +956,11 @@ un.f = 0.0; // 把数值设为浮点形式
## 10.4. 合并结构
SystemVerilog 提供的合并结构允许对数据在存储器中的排布方式有更多的控制。合并结构是以连续比特集的方式存放的,中间没有闲置的空间。[例 2. 37](#例2. 37) 中的 ` pixel` 结构使用了三个数值,所以它占用了三个长字的存储空间,即使它实际只需要三个字节。可以指定把它合并到尽可能小的空间里。
SystemVerilog 提供的合并结构允许对数据在存储器中的排布方式有更多的控制。合并结构是以连续比特集的方式存放的,中间没有闲置的空间。[例 37](#例37) 中的 ` pixel` 结构使用了三个数值,所以它占用了三个长字的存储空间,即使它实际只需要三个字节。可以指定把它合并到尽可能小的空间里。
<span id="例2. 40"></span >
<a id="例40"></a >
` `` verilog 例 2. 40 合并结构
` `` verilog 例 40 合并结构
typedef struct packed {bit [7:0] r, g, b;} pixel_p_s;
pixel_p_s my_pixel;
` ``
@@ -981,11 +977,11 @@ SystemVerilog 数据类型的多样性意味着可能需要在它们之间进行
## 11.1. 静态转换
静态转换操作不对转换值进行检查。如[例 2. 41](#例2. 41) 所示,转换时指定目标类型,并在需要转换的表达式前加上单引号即可。注意,Verilog 对整数和实数类型,或者不同位宽的向量之间进行隐式转换。
静态转换操作不对转换值进行检查。如[例 41](#例41) 所示,转换时指定目标类型,并在需要转换的表达式前加上单引号即可。注意,Verilog 对整数和实数类型,或者不同位宽的向量之间进行隐式转换。
<span id="例2. 41"></span >
<a id="例41"></a >
` `` verilog 例 2. 41 在整型和实划之间进行静态转换
` `` verilog 例 41 在整型和实划之间进行静态转换
int i;
real r;
@@ -999,11 +995,11 @@ r = real'(42); // 转换是非强制的
## 11.3. 流操作符
流操作符 ` <<` 和 ` >>` 用在赋值表达式的右边,后面带表达式、结构或数组。流操作符用于把其后的数据打包成一个比特流。操作符 ` >>` 把数据从左至右变成流,而 ` <<` 则把数据从右至左变成流,如[例 2. 42](#例2. 42) 所示。也可以指定一个片段宽度,把源数据按照这个宽度分段以后再转变成流。不能将比特流结果直接赋给非合并数组,而应该在赋值表达式的左边使用流操作符把比特流拆分到非合并数组中。
流操作符 ` <<` 和 ` >>` 用在赋值表达式的右边,后面带表达式、结构或数组。流操作符用于把其后的数据打包成一个比特流。操作符 ` >>` 把数据从左至右变成流,而 ` <<` 则把数据从右至左变成流,如[例 42](#例42) 所示。也可以指定一个片段宽度,把源数据按照这个宽度分段以后再转变成流。不能将比特流结果直接赋给非合并数组,而应该在赋值表达式的左边使用流操作符把比特流拆分到非合并数组中。
<span id="例2. 42"></span >
<a id="例42"></a >
` `` verilog 例 2. 42 基本的流操作
` `` verilog 例 42 基本的流操作
initial begin
int h;
bit [7:0] b, g[4], j[4] = '{8'ha, 8'hb, 8'hc, 8'hd};
@@ -1022,11 +1018,11 @@ end
也可以使用很多连接符 ` {}` 来完成同样的操作,但是流操作符用起来会更简洁并且易于阅读。
如果需要打包或拆分数组,可以使用流操作符来完成具有不同尺寸元素的数组间的转换。例如,可以将字节数组转换成字数组。对于定宽数组、动态数组和队列都可以这样。[例 2. 43](#例2. 43) 示范了队列之间的转换,这种转换同时也适用于动态数组。数组元素会根据需要自动分配。
如果需要打包或拆分数组,可以使用流操作符来完成具有不同尺寸元素的数组间的转换。例如,可以将字节数组转换成字数组。对于定宽数组、动态数组和队列都可以这样。[例 43](#例43) 示范了队列之间的转换,这种转换同时也适用于动态数组。数组元素会根据需要自动分配。
<span id="例2. 43"></span >
<a id="例43"></a >
` `` verilog 例 2. 43 使用流操作符进行队列间的转换
` `` verilog 例 43 使用流操作符进行队列间的转换
initial begin
bit [15:0] wq[$] = {16'h1234, 16'h5678};
bit [7:0] bq[$];
@@ -1042,11 +1038,11 @@ end
> 数组下标失配是在数组间进行流操作时常见的错误。数组声明中的下标 ` [256]` 等同于 ` [0:255]` 而非 ` [255:0]`。由于很多数组使用 ` [high:low]` (由高到低) 的下标形式进行声明,使用流操作把它们的值赋给带 ` [size]` 下标形式的数组,会造成元素倒序。同样,如果把声明形式为 ` bit [7:0] src [255:0]` 的非合并数组使用流操作赋值给声明形式为 ` bit [7:0] [255:0] dst` 的合并数组,则数值的顺序会被打乱。对于合并的字节数组,正确的声明形式应该是 ` bit [255:0] [7:0] dst`。
流操作符也可用来将结构 (例如,ATM 信元) 打包或拆分到字节数组中。在[例 2. 44](#例2. 44) 中使用流操作把结构转换成动态的字节数组,然后字节数组又被反过来转换成结构。
流操作符也可用来将结构 (例如,ATM 信元) 打包或拆分到字节数组中。在[例 44](#例44) 中使用流操作把结构转换成动态的字节数组,然后字节数组又被反过来转换成结构。
<span id="例2. 44"></span >
<a id="例44"></a >
` `` verilog 例 2. 44 使用流操作符在结构和数组间进行转换
` `` verilog 例 44 使用流操作符在结构和数组间进行转换
initial begin
typedef struct {int a;
byte b;
@@ -1072,19 +1068,19 @@ end
在学会使用枚举类型之前,只能使用文本宏。宏的作用范围太大,而且大多数情况下对于调试者是可见的。枚举创建了一种强大的变量类型,它仅限于一些特定名称的集合,例如指令中的操作码或者状态机中的状态名。例如,使用 ADD、MOVE 或 ROTW 这些名称有利于编写和维护代码,它比直接使用 ` 8'h01` 这样的常量或者宏要好得多。定义常量的另一种方法是使用参数。但参数需要对每个数值进行单独的定义,而枚举类型却能够自动为列表中的每个名称分配不同的数值。
最简单的枚举类型声明包含了一个常量名称列表以及一个或多个变量,如[例 2. 45](#例2. 45) 所示。通过这种方式创建的是一个匿名的枚举类型,它只能用于这个例子中声明的变量。
最简单的枚举类型声明包含了一个常量名称列表以及一个或多个变量,如[例 45](#例45) 所示。通过这种方式创建的是一个匿名的枚举类型,它只能用于这个例子中声明的变量。
<span id="例2. 45"></span >
<a id="例45"></a >
` `` verilog 例 2. 45 一个简单的枚举类型
` `` verilog 例 45 一个简单的枚举类型
enum {RED, BLUE, GREEN} color;
` ``
创建一个署名的枚举类型有利于声明更多新变量,尤其是当这些变量被用作子程序参数或模块端口时。首先需要创建枚举类型,然后再创建相应的变量。使用内建的 ` name()` 函数,可以得到枚举变量值对应的字符串,如[例 2. 46](#例2. 46) 所示。
创建一个署名的枚举类型有利于声明更多新变量,尤其是当这些变量被用作子程序参数或模块端口时。首先需要创建枚举类型,然后再创建相应的变量。使用内建的 ` name()` 函数,可以得到枚举变量值对应的字符串,如[例 46](#例46) 所示。
<span id="例2. 46"></span >
<a id="例46"></a >
` `` verilog 例 2. 46 枚举类型
` `` verilog 例 46 枚举类型
// 创建代表 0,1,2 的数据类型
typedef enum {INIT, DECODE, IDLE} fsmstate_e;
fsmstate_e pstate, nstate; // 声明自定义类型变量
@@ -1104,28 +1100,28 @@ end
## 12.1. 定义枚举值
枚举值默认为从 0 开始递增的整数。可以定义自己的枚举值。[例 2. 47](#例2. 47) 中使用 ` INIT` 代表默认值 0, ` DECODE` 代表 2, ` IDLE` 代表 3。
枚举值默认为从 0 开始递增的整数。可以定义自己的枚举值。[例 47](#例47) 中使用 ` INIT` 代表默认值 0, ` DECODE` 代表 2, ` IDLE` 代表 3。
<span id="例2. 47"></span >
<a id="例47"></a >
` `` verilog 例 2. 47 指定枚举值
` `` verilog 例 47 指定枚举值
typedef enum {INIT, DECODE=2, IDLE} fsmtype_e;
` ``
枚举常量,如[例 2. 47](#例2. 47) 中的 ` INIT`,它们的作用范围规则和变量是一样的。因此,如果在不同的枚举类型中用到了同一个枚举常量名,例如把 ` INIT` 用于不同的状态机中,那么必须在不同的作用域里声明它们,例如模块、程序块、函数和类。
枚举常量,如[例 47](#例47) 中的 ` INIT`,它们的作用范围规则和变量是一样的。因此,如果在不同的枚举类型中用到了同一个枚举常量名,例如把 ` INIT` 用于不同的状态机中,那么必须在不同的作用域里声明它们,例如模块、程序块、函数和类。
> 如果没有特别指出,枚举类型会被当成 ` int` 类型存储。由于 ` int` 类型的默认值是 0,所以在给枚举常量赋值时务必小心。在[例 2. 48](#例2. 48) 中 ` position` 会被初始化为 0,这并不是一个合法的 ` ordinal_e` 变量。这种情况是语言本身所规定的,而非工具上的缺陷。因此把 0 指定给一个枚举常量可以避免这个错误,如[例 2. 49](#例2. 49) 所示。
> 如果没有特别指出,枚举类型会被当成 ` int` 类型存储。由于 ` int` 类型的默认值是 0,所以在给枚举常量赋值时务必小心。在[例 48](#例48) 中 ` position` 会被初始化为 0,这并不是一个合法的 ` ordinal_e` 变量。这种情况是语言本身所规定的,而非工具上的缺陷。因此把 0 指定给一个枚举常量可以避免这个错误,如[例 49](#例49) 所示。
<span id="例2. 48"></span >
<a id="例48"></a >
` `` verilog 例 2. 48 指定枚举值:不正确
` `` verilog 例 48 指定枚举值:不正确
typedef enum {FIRST=1, SECOND, THIRD} ordinal_e;
ordinal_e position;
` ``
<span id="例2. 49"></span >
<a id="例49"></a >
` `` verilog 例 2. 49 指定枚举值:正确
` `` verilog 例 49 指定枚举值:正确
typedef enum {BAD_O=0, FIRST=1, SECOND, THIRD} ordinal_e;
ordinal_e position;
` ``
@@ -1147,11 +1143,11 @@ SystemVerilog 提供了一些可以遍历枚举类型的函数:
件。如果使用 ` current! =current.last`,则循环会在到达最后一个成员之前终止。如果
使用 ` current<=current.last`,则会造成死循环,因为 ` next` 给出的值永远也不会大于最后一个值。这类似于 ` for` 循环的步长为 0.3,而循环变量定义为 ` bit [1:0]`,所以循环永远不会退出。
实际上,可以使用 ` do ... while` 循环来遍历所有值,如[例 2. 50](#例2. 50) 所示。
实际上,可以使用 ` do ... while` 循环来遍历所有值,如[例 50](#例50) 所示。
<span id="例2. 50"></span >
<a id="例50"></a >
` `` verilog 例 2. 50 遍历所有枚举成员
` `` verilog 例 50 遍历所有枚举成员
typedef enum {RED, BLUE, GREEN} color_e;
color_e color;
color = color.first;
@@ -1167,9 +1163,9 @@ while (color != color.first); // 环形绕回时即完成
枚举类型的默认类型为双状态 ` int`。可以使用简单的赋值表达式把枚举变量的值直接赋给非枚举变量如 ` int`。但 SystemVerilog 不允许在没有进行显式类型转换的情况下把整型变量赋给枚举变量。SystemVerilog 要求显式类型转换的原因是**可能存在的数值越界情况**。
<span id="例2. 51"></span >
<a id="例51"></a >
` `` verilog 例 2. 51 整型和枚举类型之间的相互赋值
` `` verilog 例 51 整型和枚举类型之间的相互赋值
typedef enum {RED, BLUE, GREEN} COLOR_E;
COLOR_E color, c2;
int c;
@@ -1187,9 +1183,9 @@ initial begin
end
` ``
在[例 2. 51](#例2. 51) 中,` $cast` 被当成函数进行调用,目的在于把右边的值赋给左边的量。如果赋值成功,` $cast()` 返回 1。如果因为数值越界而导致赋值失败,则不进行任何赋值,函数返回 0。如果把 ` cast` 当成任务使用并且操作失败,则 SystemVerilog 会打印错误信息。
在[例 51](#例51) 中,` $cast` 被当成函数进行调用,目的在于把右边的值赋给左边的量。如果赋值成功,` $cast()` 返回 1。如果因为数值越界而导致赋值失败,则不进行任何赋值,函数返回 0。如果把 ` cast` 当成任务使用并且操作失败,则 SystemVerilog 会打印错误信息。
也可以像[例 2. 51](#例2. 51) 所示的那样使用 ` type'(val)` 进行类型转换,但这种方式并不进行任何类型检查,所以转换结果可能会越界。例如,在[例 2. 51](#例2. 51) 中进行静态类型转换以后,赋给 ` c2` 的值实际上已经越界,所以应该尽量避免使用这种方式。
也可以像[例 51](#例51) 所示的那样使用 ` type'(val)` 进行类型转换,但这种方式并不进行任何类型检查,所以转换结果可能会越界。例如,在[例 51](#例51) 中进行静态类型转换以后,赋给 ` c2` 的值实际上已经越界,所以应该尽量避免使用这种方式。
# 13. 常量
@@ -1199,26 +1195,26 @@ SystemVerilog 中有好几种类型的常量。Verilog 中创建常量的最典
SystemVerilog 也支持 ` const` 修饰符,允许在变量声明时对其进行初始化,但不能在过程代码中改变其值。
<span id="例2. 52"></span >
<a id="例52"></a >
` `` verilog 例 2. 52 <code>const</code> 变量的声明
` `` verilog 例 52 <code>const</code> 变量的声明
initial begin
const byte colon = ":";
...
end
` ``
在[例 2. 52](#例2. 52) 中,` colon` 的值在 ` initial` 块开头就被初始化。` const` 还可以作为子程序参数。
在[例 52](#例52) 中,` colon` 的值在 ` initial` 块开头就被初始化。` const` 还可以作为子程序参数。
# 14. 字符串
Verilog 中使用 ` reg` 变量来保存字符串,这一表达很不方便。SystemVerilog 中的 ` string` 类型可以用来保存长度可变的字符串。单个字符是 ` byte` 类型。长度为 $N$ 的字符串中,元素编号从 0 到 $N — 1$。注意,跟 C 语言 不一样的是,字符串的结尾并不带标识符 null,所有尝试使用字符 “\0” 的操作都会被忽略。字符串使用动态的存储方式,所以不用担心存储空间会全部用完。
[例 2. 53](#例2. 53) 示范了与字符串相关的几种操作。函数 ` getc(N)` 返回位置 ` N` 上的字节,` toup per` 返回一个所有字符大写的字符串,` tolower` 返回一个小写的字符串。大括号 ` {}` 用于串接字符串。任务 ` putc(M,C)` 把字节 ` c` 写到字符串的 ` M` 位上。` M` 必须介于 0 和 ` len` 所给出的长度之间。函数 ` substr(start,end)` 提取出从位置 ` start` 到 ` end` 之间的所有字符。
[例 53](#例53) 示范了与字符串相关的几种操作。函数 ` getc(N)` 返回位置 ` N` 上的字节,` toup per` 返回一个所有字符大写的字符串,` tolower` 返回一个小写的字符串。大括号 ` {}` 用于串接字符串。任务 ` putc(M,C)` 把字节 ` c` 写到字符串的 ` M` 位上。` M` 必须介于 0 和 ` len` 所给出的长度之间。函数 ` substr(start,end)` 提取出从位置 ` start` 到 ` end` 之间的所有字符。
<span id="例2. 53"></span >
<a id="例53"></a >
` `` verilog 例 2. 53 字符串方法
` `` verilog 例 53 字符串方法
string s;
initial begin
@@ -1241,20 +1237,20 @@ task my_log(string message);
endtask
` ``
稍加留意便可发现动态字符串的用处有多大。在别的语言如 C 里,必须不停地创建临时字符串来接收函数返回的结果。在[例 2. 53](#例2. 53) 中,函数 ` psprintf()` 替代了 Verilog-2001 中的函数 ` sformat()`。这个新函数返回一个格式化的临时字符串,并且可以直接传递给其他子程序。这样就可以不用定义新的临时字符串并在格式化语句与函数调用过程中传递这个字符串。
稍加留意便可发现动态字符串的用处有多大。在别的语言如 C 里,必须不停地创建临时字符串来接收函数返回的结果。在[例 53](#例53) 中,函数 ` psprintf()` 替代了 Verilog-2001 中的函数 ` sformat()`。这个新函数返回一个格式化的临时字符串,并且可以直接传递给其他子程序。这样就可以不用定义新的临时字符串并在格式化语句与函数调用过程中传递这个字符串。
# 15. 表达式的位宽
在 Verilog 中,表达式的位宽是造成行为不可预知的主要原因之一。[例 2. 54](#例2. 54) 使用四种不同方式实现 ` 1+1`:
在 Verilog 中,表达式的位宽是造成行为不可预知的主要原因之一。[例 54](#例54) 使用四种不同方式实现 ` 1+1`:
- 方式 A 使用两个单比特变量,在这种精度下得到 ` 1+1=0`。
- 方式 B 由于赋值表达式的左边有一个 8 比特的变量,所以其精度是 8 比特,得到的结果是 ` 1+1=2`。
- 方式 C 采用一个哑元常数强迫 SystemVerilog 使用 2 比特精度。
- 方式 D 中,第一个值在转换符的作用下被指定为 2 比特的值,所以结果是 ` 1+1=2`。
<span id="例2. 54"></span >
<a id="例54"></a >
` `` verilog 例 2. 54 表达式位宽依赖于上下文
` `` verilog 例 54 表达式位宽依赖于上下文
bit [7:0] b8;
bit one = 1'b1; // 单比特
$displayb(one + one); // A: 1+1 = 0