diff --git a/source/_posts/2022-05-30-josh-systemverilog-1.md b/source/_posts/2022-05-30-josh-systemverilog-1.md index 712224b..6649840 100644 --- a/source/_posts/2022-05-30-josh-systemverilog-1.md +++ b/source/_posts/2022-05-30-josh-systemverilog-1.md @@ -24,7 +24,7 @@ categories: 验证工程师能学到的最重要的原则是“Bugs are good”,不要因为害怕 bug 而不敢 debug,每次找到 bug 都应该及时告警并记录下来。任何一个项目中都不可能没有 bug,所以在流片之前每发现一个 bug 都意味着用户手中的产品少一个 bug。我们应该尽可能细致深入地去检验设计,找出所有可能存在的 bug,尽管有时候这些 bug 可能很容易修复。 -本文需要 Verilog 语言基础,面向对 SystemVerilog HVL (Hardware Verification Language, 硬件验证语言) 感兴趣的读者。与 HDL (Hardware Desription Language, 硬件描述语言) 相比,HVL 具有一些特别的性质: +本文需要 Verilog 语言基础,面向对 SystemVerilog HVL (Hardware Verification Language, 硬件验证语言) 感兴趣的读者。与 HDL (Hardware Description Language, 硬件描述语言) 相比,HVL 具有一些特别的性质: 1. **约束下的随机激励生成** (*Constrained-random stimulus generation*)。 2. **功能覆盖率** (*Functional coverage*)。 @@ -86,21 +86,19 @@ Testbench 的用途在于**确定 DUT 的正确性**。包含下列步骤: 这种渐进的方法比较容易取得稳步的进展,因而很受那些喜欢看到项目持续向前推进管理者的欢迎。由于创建每个激励向量时并不需要什么基础,所以定向测试的结果也会很快得到。只要给予足够的时间和人力,定向测试对于大部分设计验证来讲都是可以胜任的。 -[图 1.1](#图1.1) 显示了定向测试如何逐步覆盖验证计划中的每个特性。每个测试都针对一个特别的设计元素集合。**如果有足够的时间**,可以写出 100% 覆盖整个验证计划所需要的全部测试。 +[图 1](#图1) 显示了定向测试如何逐步覆盖验证计划中的每个特性。每个测试都针对一个特别的设计元素集合。**如果有足够的时间**,可以写出 100% 覆盖整个验证计划所需要的全部测试。 - -
-
-
-
-
-
-
-
-
-
-
-
-
-logic 类型的使用
+``` verilog 例 1 logic 类型的使用
module logic_data_type(input logic rst_h);
- parameter CYCLE = 20;
- logic q, q_l, d, clk, rst_l;
- initial begin
- clk = 0; // 过程赋值
- forever #(CYCLE/2) clk = ~clk;
- end
+ parameter CYCLE = 20;
+ logic q, q_l, d, clk, rst_l;
+ initial begin
+ clk = 0; // 过程赋值
+ forever #(CYCLE/2) clk = ~clk;
+ end
- assign rst_l = ~rst_h; // 连续赋值
- not n1(q_l, q); // q_l 被门驱动
- my_dff d1(q, d, clk, rst_l); // q 被模块驱动
+ assign rst_l = ~rst_h; // 连续赋值
+ not n1(q_l, q); // q_l 被门驱动
+ my_dff d1(q, d, clk, rst_l); // q 被模块驱动
endmodule
```
@@ -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) 所示。
-
+
-``` 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)。
-
+
-``` 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 语言类似。
-
+
-``` 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 中已经引入,但这种紧凑型声明方式却是新的。
-
+
-``` 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` 被存放到三个字的空间里。
-
+
-``` verilog 例 2.6 非合并数组的声明
+``` verilog 例 6 非合并数组的声明
bit [7:0] b_unpack[3]; // 非合并数组
```
-
-
-for 和 foreach 循环
+``` verilog 例 8 在数组操作中使用 for 和 foreach 循环
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]`。
-
+
-``` 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) 所示。
-
+
-``` 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) 把一个二维数组打印成一个方形的阵列。它在外层循环中遍历第一个维度,然后在内层循环中遍历第二个维度。
-
+
-``` 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) 所示。
-
+
-``` 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]`,它实际上产生了一个有四个元素的临时数组。
-
+
-``` 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)。
-
+
-``` 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 所示。
-
+
-``` 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”
```
-
-
-
-
-min、max、unique
+``` verilog 例 25 数组定位方法:min、max、unique
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) 所示。
-
+
-``` verilog 例 2.26 数组定位方法:find
+``` verilog 例 26 数组定位方法:find
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) 所示。
-
+
-``` 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`) 则使用条件操作符 `? :` 进行计算。
-
+
-``` 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) 所示。注意,与前述数组定位方法不同的是,**排序方法改变了原始数组**,而**数组定位方法新建了一个队列来保存结果**。
-
+
-``` 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) 示范了如何使用子域对一个结构进行排序。结构和合并结构后面会有解释。
-
+
-``` 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` 创建结构。
-
+
-``` 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) 所示。
-
+
-``` verilog 例 2.32 Verilog 中用户自定义的类型宏
+``` verilog 例 32 Verilog 中用户自定义的类型宏
// 旧的 Verilog 风格
`define OPSIZE 8
`define OPREG reg [`OPSIZE-1:0]
@@ -857,9 +853,9 @@ typedef 语句可以用来创建新的类型。例如,要求一个算术逻辑
这种情况并没有创建新的类型,而只是在进行文本替换。在 SystemVerilog 中,采用下面的代码可以创建新的类型。本文约定,所有用户自定义类型都带后缀 “`_t`”。
-
+
-``` 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` 的定义放到通用定义程序包中,这样就可以在仿真程序的任何地方使用它。
-
+
-``` verilog 例 2.34 uint 的定义
+``` verilog 例 34 uint 的定义
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) 接着声明了一个这种类型的数组并进行了初始化。
-
+
-``` 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` 的结构,它有三个无符号的字节变量,分别代表红、绿和蓝。
-
+
-``` verilog 例 2.36 创建一个 pixel 类型
+``` verilog 例 36 创建一个 pixel 类型
struct {bit [7:0] r, g, b;} pixel;
```
-[例 2.36](#例2.36) 中的声明只是创建了一个 `pixel` 变量。要想在端口和程序中共享它,则必须创建一个新的类型,如[例 2.37](#例2.37) 所示。
+[例 36](#例36) 中的声明只是创建了一个 `pixel` 变量。要想在端口和程序中共享它,则必须创建一个新的类型,如[例 37](#例37) 所示。
-
+
-``` verilog 例 2.37 pixel 结构
+``` verilog 例 37 pixel 结构
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) 所示,赋值时要把数值放到带单引号的大括号中。
-
+
-``` verilog 例 2.38 对 struct 类型进行初始化
+``` verilog 例 38 对 struct 类型进行初始化
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` 存放在同一位置上。
-
+
-``` verilog 例 2.39 使用一个下标 typedef 创建联合
+``` verilog 例 39 使用一个下标 typedef 创建联合
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` 结构使用了三个数值,所以它占用了三个长字的存储空间,即使它实际只需要三个字节。可以指定把它合并到尽可能小的空间里。
-
+
-``` 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 对整数和实数类型,或者不同位宽的向量之间进行隐式转换。
-
+
-``` verilog 例 2.41 在整型和实划之间进行静态转换
+``` verilog 例 41 在整型和实划之间进行静态转换
int i;
real r;
@@ -999,11 +995,11 @@ r = real'(42); // 转换是非强制的
## 11.3. 流操作符
-流操作符 `<<` 和 `>>` 用在赋值表达式的右边,后面带表达式、结构或数组。流操作符用于把其后的数据打包成一个比特流。操作符 `>>` 把数据从左至右变成流,而 `<<` 则把数据从右至左变成流,如[例 2.42](#例2.42) 所示。也可以指定一个片段宽度,把源数据按照这个宽度分段以后再转变成流。不能将比特流结果直接赋给非合并数组,而应该在赋值表达式的左边使用流操作符把比特流拆分到非合并数组中。
+流操作符 `<<` 和 `>>` 用在赋值表达式的右边,后面带表达式、结构或数组。流操作符用于把其后的数据打包成一个比特流。操作符 `>>` 把数据从左至右变成流,而 `<<` 则把数据从右至左变成流,如[例 42](#例42) 所示。也可以指定一个片段宽度,把源数据按照这个宽度分段以后再转变成流。不能将比特流结果直接赋给非合并数组,而应该在赋值表达式的左边使用流操作符把比特流拆分到非合并数组中。
-
+
-``` 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) 示范了队列之间的转换,这种转换同时也适用于动态数组。数组元素会根据需要自动分配。
-
+
-``` 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) 中使用流操作把结构转换成动态的字节数组,然后字节数组又被反过来转换成结构。
-
+
-``` 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) 所示。通过这种方式创建的是一个匿名的枚举类型,它只能用于这个例子中声明的变量。
-
+
-``` verilog 例 2.45 一个简单的枚举类型
+``` verilog 例 45 一个简单的枚举类型
enum {RED, BLUE, GREEN} color;
```
-创建一个署名的枚举类型有利于声明更多新变量,尤其是当这些变量被用作子程序参数或模块端口时。首先需要创建枚举类型,然后再创建相应的变量。使用内建的 `name()` 函数,可以得到枚举变量值对应的字符串,如[例 2.46](#例2.46) 所示。
+创建一个署名的枚举类型有利于声明更多新变量,尤其是当这些变量被用作子程序参数或模块端口时。首先需要创建枚举类型,然后再创建相应的变量。使用内建的 `name()` 函数,可以得到枚举变量值对应的字符串,如[例 46](#例46) 所示。
-
+
-``` 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。
-
+
-``` 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) 所示。
-
+
-``` verilog 例 2.48 指定枚举值:不正确
+``` verilog 例 48 指定枚举值:不正确
typedef enum {FIRST=1, SECOND, THIRD} ordinal_e;
ordinal_e position;
```
-
+
-``` 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) 所示。
-
+
-``` 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 要求显式类型转换的原因是**可能存在的数值越界情况**。
-
+
-``` 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` 修饰符,允许在变量声明时对其进行初始化,但不能在过程代码中改变其值。
-
+
-``` verilog 例 2.52 const 变量的声明
+``` verilog 例 52 const 变量的声明
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` 上的字节,`toupper` 返回一个所有字符大写的字符串,`tolower` 返回一个小写的字符串。大括号 `{}` 用于串接字符串。任务 `putc(M,C)` 把字节 `c` 写到字符串的 `M` 位上。`M` 必须介于 0 和 `len` 所给出的长度之间。函数 `substr(start,end)` 提取出从位置 `start` 到 `end` 之间的所有字符。
+[例 53](#例53) 示范了与字符串相关的几种操作。函数 `getc(N)` 返回位置 `N` 上的字节,`toupper` 返回一个所有字符大写的字符串,`tolower` 返回一个小写的字符串。大括号 `{}` 用于串接字符串。任务 `putc(M,C)` 把字节 `c` 写到字符串的 `M` 位上。`M` 必须介于 0 和 `len` 所给出的长度之间。函数 `substr(start,end)` 提取出从位置 `start` 到 `end` 之间的所有字符。
-
+
-``` 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`。
-
+
-``` verilog 例 2.54 表达式位宽依赖于上下文
+``` verilog 例 54 表达式位宽依赖于上下文
bit [7:0] b8;
bit one = 1'b1; // 单比特
$displayb(one + one); // A: 1+1 = 0