第二章 数据类型

与Verilog相比,SystemVerilog提供了许多改进的数据结构。其中一些是为设计人员创建的,但也可以用于testbench。在本章中,您将了解对验证最有用的数据结构。

System Verilog引入了具有以下优点的新数据类型。

  • 两种状态:更好的性能,减少内存使用

  • 队列、动态数组和关联数组:减少内存使用,内置搜索和排序支持

  • 类和结构:支持抽象数据结构

  • 联合和打包结构:允许对同一数据的多个视图

  • string:内置字符串支持

  • 枚举类型:代码更容易编写和理解

2.1 内置的数据类型

Verilog-1995有两种基本数据类型:变量和网络,它们都包含4种状态值:0、1、Z和x。RTL代码使用变量来存储组合值和序列值。变量可以是无符号的单位或多位(reg [7:0] m)、有符号的32位变量(整数)、无符号的64位变量(时间)和浮点数(实数)。变量可以被组合成具有固定大小的数组。网络用于连接设计的各个部分,如门原语和模块实例。网有多种形式,但大多数设计人员使用标量和向量线来连接设计块的端口。最后,所有的存储都是静态的,这意味着在整个模拟中所有的变量都是活的,例程不能使用堆栈来保存参数和局部值。Verilog-2001允许您在静态和动态存储(比如堆栈)之间切换。

SystemVerilog添加了许多新的数据类型,以帮助硬件设计人员和验证工程师。

2.1.1 逻辑类型

在Verilog中,总是让新用户摸不着头脑的一件事是reg和wire之间的区别。当驾驶一个端口时,你应该使用哪个?那么当你连接方块的时候呢?SystemVerilog改进了经典的reg数据类型,除了作为一个变量外,它还可以由连续赋值、门和模组驱动。它被赋予了同义词逻辑,就像一些刚接触Verilog的人认为的那样,reg声明了一个数字寄存器,而不是一个信号。逻辑信号可以在任何使用网络的地方使用,除非逻辑变量不能由多个结构驱动程序驱动,例如当你为双向总线建模时。在本例中,变量需要是net类型,比如wire,以便SystemVerilog能够解析多个值以确定最终值。

样例2.1显示了SystemVerilog逻辑类型。

使用逻辑类型的示例2.1

您可以使用逻辑类型来查找网络列表bug,因为这种类型只能有一个驱动程序。与其尝试在reg和wire之间进行选择,不如将所有信号声明为逻辑,如果有多个驱动程序,就会得到编译错误。当然,任何你想要有多个驱动程序的信号,比如一个双向总线,应该用一个网络类型来声明,比如wire或tri。

2.1.2 国数据类型

与声明为4状态类型的变量相比,SystemVerilog引入了几种2状态数据类型,以改进模拟器性能并减少内存使用。最简单的类型是位,它总是无符号的。有四种有符号的两状态类型:byte、shortint、int和longint。如示例2.2所示。

示例2.2签名数据类型

您可能想使用byte之类的类型来替换更详细的声明,如logic[7:0]。硬件设计人员应该小心,因为这些新类型是有符号变量,所以字节变量最多只能数到127,而不能数到255

期望。(取值范围为−128 ~ +127)您可以使用无符号字节,但这比仅使用bit[7:0]要详细得多。如第6章所述,带符号的变量也会导致随机化的意外结果。

将2-状态变量连接到被测试的设计时要小心,特别是它的输出。如果硬件试图驱动X或Z,这些值将被转换为2状态值,您的testbench代码可能永远不会知道。不要试图记住它们是否被转换为0或1;相反,总是检查未知值的传播。

使用$isunknown()操作符,如果表达式的任意位是X或Z,则返回1,如示例2.3所示。

示例2.3检查4个状态的值

格式%0t和参数$time打印当前的模拟时间,用$timeformat()例程指定。时间值将在3.7节中更详细地讨论。

2.2 固定大小的数组

除了一维的、固定大小的Verilog-1995数组之外,SystemVerilog还提供了几种样式的数组。此外,还添加了许多新特性来支持这些数据类型。

2.2.1 声明和初始化固定大小的数组

Verilog要求必须在声明中给出数组的高低限制。由于几乎所有数组都使用0的低索引,因此SystemVerilog允许您使用只给出数组大小的快捷方式,这类似于C的风格,如示例2.4所示。

示例2.4声明固定大小的数组

如何计算处理给定数组大小所需的位数?SystemVerilog具有$clog2()函数,用于计算以2为基数的log的上限,如示例2.5所示。

示例2.5计算内存的地址宽度

通过在变量名之后指定维度,可以创建多维固定大小的数组。样例2.6创建几个整数的二维数组,8个条目乘4,并将最后一个条目设置为1。多维数组是在Verilog-2001中引入的,但是紧凑声明样式是新的。

样例2.6声明和使用多维数组

如果您的代码意外地尝试从越位地址读取,System- Verilog将返回数组元素类型的默认值。这只是意味着包含4种状态类型的数组(如logic)将返回X,而包含2种状态类型的数组(如int或bit)将返回0。这适用于所有的数组类型-固定的,动态的,关联的,或队列,如果你的地址有X或Z,也适用。

许多SystemVerilog模拟器将每个元素存储在32位的单词边界上。因此,byte、shortint和int都存储在单个单词中,而longint则存储在两个单词中。

未打包的数组,例如示例2.7中所示的数组,将值存储在单词的下半部分,而上半部分是未使用的。字节数组b_unpack以三个单词的形式存储,如图2.1所示。

样例2.7解包的数组声明

b_unpack [0] b_unpack [1] b_unpack [2]

图2.1未封装阵列存储

打包的数组在2.2.6节中解释。

模拟器通常在两个或多个连续的单词中存储4种状态类型,如逻辑和整数,使用两倍的存储作为2状态变量。

2.2.2 数组的文字

示例2.8展示了如何使用数组字面量初始化数组,该字面量是一个apos- trophe,后跟花括号中的值。(这不是用于编译器指令和宏的重音表。)您可以一次设置一些或所有元素。您可以通过在花括号前放置一个计数来复制值。

样例2.8初始化数组

注意,在示例2.8中,数组ascend的声明包含了一个初始值。2009 LRM声明这些变量必须在静态块中声明,或者使用static关键字。因为这本书建议总是声明你的测试模块和程序为自动,你需要添加静态关键字的声明加上初始化,当它是在一个初始块。

2009年LRM的一个很棒的新特性是使用%p格式说明符进行打印。这将打印一个与数据对象的值等价的赋值模式。您可以在SystemVerilog中打印任何数据类型,包括数组、结构、类等等。样例2.9演示了如何打印带有%p格式说明符的数组。

样品2.9打印,%p打印说明

2.2.3 基本的数组操作——for和Foreach

操作数组最常见的方法是使用for或foreach循环。在样例2.10中,变量i被声明为for循环的局部变量。SystemVerilog函数$size返回数组的大小。在foreach循环中,您在方括号中指定数组名称和索引,然后SystemVerilog自动遍历数组的所有元素。索引变量会自动为您声明,并且是循环的局部变量。

示例2.10使用带有for-和foreach循环的数组

注意,在样例2.11中,多维数组的foreach循环的语法可能与您预期的不同。不是用单独的方括号[i][j]列出每个下标,而是用逗号:[i,j]组合起来。

样例2.11初始化并逐步遍历多维数组

示例2.11的输出如示例2.12所示。

示例2.12打印多维数组值的输出

如果不需要单步遍历所有维度,可以省略foreach循环中的某些维度。示例2.13打印一个矩形中的二维数组。它在外部循环中执行第一个维度,然后在内部循环中执行第二个维度。

示例2.13打印多维数组

示例2.13产生示例2.14中所示的输出。

示例2.14打印多维数组值的输出

最后,foreach循环使用原始声明中的范围进行迭代。数组f[5]等价于f[0:4], foreach (f[i])等价于for (int i=0;我+ +)。对于数组rev[6:2], foreach(rev[i])语句等价于for(int i=6;我> = 2;我——)。

2.2.4 基本的数组操作-复制和比较

您可以在没有循环的情况下执行数组的聚合比较和复制。(聚合操作适用于整个数组,而不是仅适用于单个元素。)比较仅限于公正的平等和不平等。示例2.15展示了几个比较示例。的吗?:条件运算符是一个小型的if-else状态。在样例2.15中,它用于在两个字符串之间进行选择。最后的比较使用了一个数组切片src[1:4],它创建了一个包含4个元素的临时数组。

样例2.15数组复制和比较操作

在不同大小的固定数组之间复制会导致编译错误。不能在数组上执行加法或减法等聚合算法,例如a = b + c。相反,使用foreach循环。对于像xor这样的逻辑操作,必须使用循环或者使用2.2.6节中描述的打包数组。

2.2.5 位和数组下标,终于在一起了

在Verilog-1995中,一个常见的问题是不能同时使用数组和位子脚本。Verilog-2001取消了对固定大小数组的这一限制。样本

输出数组的第一个元素(二进制101)、它的最低位(1)和后面两个更高位(二进制10)。

样例2.16同时使用字和位下标

尽管这个更改对SystemVerilog并不新鲜,但许多用户可能不知道Verilog-2001中的这个有用的改进。供你参考- a中的双逗号

$display语句插入一个空格。

2.2.6 拥挤的数组

对于某些数据类型,您可能既希望访问整个值,又希望将其划分为更小的元素。例如,您可能有一个32位寄存器,有时您希望将其作为4个8位值处理,而在其他时候则希望将其作为一个单独的无符号值处理。SystemVerilog打包的数组被视为数组和单个值。与未打包的数组不同,它被存储为一个没有未使用空间的连续位集。

2.2.7 包装数组的例子

填充的位和数组尺寸作为类型的一部分指定在变量名之前。这些尺寸必须指定为[msb:lsb]格式,而不是[size]。样例2.17显示了可变的bytes,一个由四个字节组成的数组,存储在一个32位的单词中,如图2.2所示。

示例2.17打包的数组声明和使用

字节

字节[3][7]

图2.2排列排列

你可以混合包装和未包装的尺寸。您可能想要创建一个表示可以以位、字节或长字访问的内存的数组。样本

2.18显示了barray,这是一个由5个封装元素组成的未封装数组,每个元素宽4个字节,如图2.3所示存储在内存中。

示例2.18混合打包/未打包数组的声明

barray [0] [3] barray [0] [1] [6]

巴林[0]巴林[1]巴林[2]巴林[3]巴林[4]

图2.3封装阵列位布局

使用单个下标,可以得到一个数据字barray[0]。有了两个下标,你得到一个字节的数据,barray[0][3]。通过三个下标,你可以访问一个单独的位,barray[0][1][6]。因为维度是在名称barray[5]之后指定的,所以该维度是未打包的,所以必须至少给出一个下标。

示例2.18的最后一行拷贝在两个打包的数组之间。因为底层的值只是位,所以即使数组有不同的维数,也可以复制。

2.2.8 选择已打包和未打包的数组

你应该选择哪个——一个已打包的数组还是一个未打包的数组?如果需要转换为标量或从标量转换,打包的数组是很方便的。例如,您可能需要将内存作为字节或单词引用。图2.3中的阵列可以满足这一要求。任何类型的数组都可以打包,包括动态数组、队列和关联数组,这些将在2.3、2.4和2.5节中解释。

如果你需要等待数组的变化,你必须使用一个打包的数组。当内存值发生变化时,您的测试工作台可能需要唤醒,所以您需要使用@操作符。这只适用于标量值和已打包数组。在2.18示例中,你可以阻塞变量lw或barray[0],但不能阻塞整个数组barray,除非你将其展开:@(barray[0]或barray[1]或barray[2]或barray[3]或barray[4])。

2.3 动态数组

到目前为止显示的基本Verilog数组类型被称为固定大小的数组,因为它的大小是在编译时设置的。如果直到运行时才知道数组的大小,该怎么办?例如,您可能希望在模拟开始时生成随机数量的事务。如果将事务存储在一个固定大小的数组中,那么该数组必须足够大,以容纳最大数量的事务,但通常会容纳更少的事务,从而浪费内存。SystemVerilog提供了一个动态数组,可以在模拟期间分配和调整数组大小,从而使模拟消耗的内存最少。

动态数组用空的字下标[]来声明。这意味着你不需要在编译时指定数组大小;相反,在运行时给出它。数组最初是空的,因此必须调用new[]构造函数来分配空间,并在方括号中传递条目的数量。如果将数组名称传递给新的[]构造函数,这些值将被复制到新的元素中,如示例2.19所示。

示例2.19使用动态数组

在样例2.19中,A行调用new[5]来分配5个数组元素。动态数组dyn现在拥有5个int值。行B将数组中每个元素的值设置为其索引值。第C行分配另一个数组并将dyn的内容复制到其中。行D和行E表示数组dyn和d2是分开的。第F行分配20个新元素,并将dyn现有的5个元素复制到数组的开头。然后重新分配原来的5个元素的dyn数组。结果是dyn指向一个20个元素的数组。最后一次调用new[]会分配100个元素,但不会复制现有的值。旧的20个元素的数组被重新分配。最后,第H行删除dyn数组。

函数的作用是:返回一个固定数组或动态数组的大小。动态数组有几个内置例程,比如delete和size。

如果您想声明一个常量数组的值,但不想麻烦地计算元素的数量,则使用带有数组字面量的动态数组。在样例2.20中有9个8位掩码元素。您应该让SystemVerilog计算它们,而不是创建一个固定大小的数组,然后意外地选择了错误的数组大小。

示例2.20为未计数列表使用动态数组

你可以在固定大小的数组和动态数组之间赋值,只要它们具有相同的基类型(比如int)。你可以将动态数组赋值给固定数组,只要它们的元素数量相同。

将固定大小的数组复制到动态数组时,SystemVerilog将调用

新建[]构造函数来分配空间,然后复制值。

您可以拥有多维动态数组,只要您在构建子数组时小心谨慎。请记住,SystemVerilog中的多维数组可以看作是其他数组的数组。首先,您需要构造最左边的维度。然后构造子数组。在示例2.21中,每个子数组都有不同的大小。

示例2.21多维动态数组

2.4 队列

SystemVerilog引入了一种新的数据类型队列,它结合了链表和数组的优点。与链表一样,您可以在队列中的任何位置添加或删除元素,而不会像动态数组那样影响性能,因为动态数组必须分配一个新的元素

数组并复制整个内容。与数组一样,您可以直接访问带有索引的任何元素,而不用像链表那样在前面插入元素的情况下进行步进操作。

队列使用包含美元符号:[$]的单词下标声明。队列的元素从0到$编号。样例2.22展示了如何使用方法从队列中添加和删除值。注意,队列字面量只有花括号,并且没有数组字面量的第一个撇号。

SystemVerilog队列类似于标准模板库的deque数据类型。您可以通过添加元素来创建队列。SystemVerilog通常会分配额外的空间,以便您可以快速插入额外的元素。如果添加的元素足够多,以至于队列耗尽了多余的空间,则SystemVerilog会自动分配更多的元素。因此,您可以在不影响动态数组性能的情况下增加或减少队列,并且SystemVerilog会为您跟踪空闲空间。注意,永远不要为队列调用new[]构造函数。

示例2.22队列方法

LRM不允许使用上述方法将队列插入到另一个队列中,尽管有些模拟器允许这样做。

可以使用单词下标和连接来代替方法。作为一种捷径,如果你把$放在一个范围的左边,例如[$:2],$代表最小值[0:2]。右边的$,如[1:$],代表最大值[1:2],在样本2.23的初始块的第一行。

示例2.23队列操作

队列元素存储在相邻的位置,因此可以有效地前后推入和弹出元素。无论队列有多大,这都需要固定的时间。在队列中间添加和删除元素需要移动现有数据以腾出空间。这样做的时间会随着队列的大小线性增长。

可以将固定或动态数组的内容复制到队列中。

2.5 关联数组

如果你偶尔想要创建一个大数组,动态数组很好,但如果你想要更大的数组呢?也许您正在为一个具有数gb地址范围的处理器建模。在一个典型的测试中,处理器可能只触及几百或几千个包含可执行代码和数据的内存位置,因此分配和初始化千兆字节的存储是浪费的。

SystemVerilog提供了关联数组,将条目存储在稀疏矩阵中。这意味着,虽然可以寻址一个非常大的地址空间,但SystemVerilog只在写入元素时为其分配内存。在下面的图片中,关联数组的值为0:3、42、1000、4521和200,000。用于存储这些数据的内存远远小于存储一个包含20万个条目的固定或动态数组所需的内存,如图2.4所示。

数据索引

0…3 42

1000 4521 200000年

图2.4关联数组

关联数组可以被模拟器存储为树或哈希表。当您需要存储具有广泛分隔的索引值的数组(例如用32位地址或64位数据值索引的数据包)时,这种额外开销是可以接受的。关联数组是用方括号中的数据类型声明的,比如[int]。或(包)。样例2.24演示了如何声明、初始化、打印和步进执行关联数组。

声明、初始化和使用关联数组

样本2.24有关联数组assoc,它的元素非常分散:1、2、4、8、16等。简单的for循环不能单步遍历它们;您需要使用foreach循环。如果你想要更好的控制,你可以在do…while循环中使用first和next函数。这些函数修改index参数,并根据数组中是否还有元素返回0或1。可以使用num或size函数查找关联数组中的元素数量。

关联数组也可以通过字符串索引寻址,类似于Perl的散列数组。样例2.25读取带有字符串的文件并构建关联数组

切换,这样您就可以快速地从字符串值映射到数字。字符串将在2.15节中得到更详细的解释。

如果试图读取尚未写入的关联数组的元素,SystemVerilog将返回数组基类型的默认值,例如0 (bit或int)表示2状态类型,X (logic)表示4状态类型。模拟器也可以给出警告信息。可以使用exists()函数检查是否分配了元素,如示例2.25所示。

示例2.25使用带有字符串索引的关联数组

可以使用数组字面量和index:element对初始化关联数组,如示例2.26所示。当您打印带有%p的数组时,元素将以相同的格式显示。

样例2.26初始化和打印关联数组

也可以使用通配符下标声明关联数组,如wild[*]。但是,不推荐使用这种样式,因为您几乎允许任何数据类型的索引。由此产生的众多问题之一是foreach循环:foreach(wild[j])中的变量j是什么类型的?整数、字符串、位还是逻辑?

2.6 数组的方法

有许多数组方法可以用于任何未打包的数组类型:固定的、动态的、队列的和关联的。这些例程可以像给出当前数组大小一样简单,也可以像对元素排序一样复杂。如果没有参数,括号是可选的。

2.6.1 数组还原方法

基本的数组缩减方法接受一个数组并将其缩减为单个值,如示例2.27所示。您可以计算所有元素的和、积或执行逻辑操作。

样例2.27数组缩减操作

其他的数组约简方法有or和xor。

SystemVerilog没有专门用于从数组中选择随机元素的方法,因此对于队列和动态数组使用索引$urandom_range(array.size()−1),对于固定数组、队列、动态和关联数组使用索引$urandom_range($size(array)−1)。关于$urandom_range的更多信息,请参阅6.10节。

如果您需要从关联数组中选择一个随机元素,则需要逐个遍历这些元素,因为没有单行方法可以访问第n个元素。样例2.28展示了如何从关联数组中选择一个随机元素

按整数索引,首先选择一个随机数,然后步进遍历数组。如果数组以字符串为索引,只需将idx的类型更改为字符串。

从关联数组中随机选择一个元素

2.6.2 数组定位方法

数组中最大的值是多少?数组是否包含特定的值?数组定位器方法在未打包的数组中查找数据。首先,您可能会想知道为什么这些函数返回一个值队列。毕竟,数组中只有一个最大值。但是,SystemVerilog需要一个队列来处理从空队列或动态数组请求值的情况。

示例2.29显示了数组定位器方法:min和max函数查找数组中最小和最大的元素。这些方法也适用于关联数组。unique方法返回数组中唯一值的队列——重复值不包括在内。

示例2.29阵列定位器方法:min, max, unique

您可以使用foreach循环搜索数组,但是SystemVerilog可以使用locator方法在一个操作中完成这一操作。with表达式告诉SystemVerilog如何执行搜索,如示例2.30所示。如果要搜索的值在数组中不存在,这些方法将返回一个空队列。

示例2.30数组定位器方法:find

在with子句中,name项被称为迭代器参数,表示数组中的单个元素。您可以将自己的名字放在数组方法的参数列表中,如示例2.31所示。

例2.31声明迭代器实参

样例2.32展示了汇总数组中值子集的各种方法。第一行将项目与7进行比较。该关系返回1 (true)或0 (false),因此计算结果是数组{1,0,1,0,0}的和。第二个函数将bool- ean结果与被测试的数组元素相乘。所以总数是{9,0,8,0,0,0}的和,也就是17。第三个计算小于8的元素的总数。第四个总数是使用?:条件操作符。最后一个数的是4。

示例2.32数组定位器方法

当您使用with子句组合一个数组缩减(如sum)时,结果可能会让您吃惊。在样例2.32中,sum运算符对

这个表达是正确的。对于样例2.32中的第一个语句,有两个大于7(9和8)的数组元素,因此将count设置为2。

返回索引(如find_ index)的数组定位器方法返回int类型的队列,而不是integer类型的队列。如果在这些语句中使用了错误的队列类型,则代码可能无法编译。

注意SystemVerilog关于操作宽度的规则。通常,如果要添加一组单个位值,SystemVer- ilog将以足够的精度进行计算,不会丢失任何位。但是sum方法使用的是数组的宽度。所以,

如果添加单个位数组的值,结果是单个位,这可能不是您预期的结果。解决方案是使用示例2.33中所示的with表达式。

样例2.33创建单个位数组的和

2.6.3 数组排序和排序

SystemVerilog有几种方法可以更改数组中元素的顺序。您可以对元素排序、颠倒它们的顺序或打乱顺序,如示例2.34所示。注意,这些方法更改了原始数组,这与第2.6.2节中的数组定位器方法不同,后者创建一个队列来保存结果。

样例2.34对数组排序

reverse和shuffle方法没有with-子句,所以它们适用于整个数组。样例2.35展示了如何按子字段对结构排序。结构和填充结构在第2.9节中解释。

样例2.35对结构数组进行排序

只有固定和动态数组,再加上队列才能排序、反转或洗牌。

不能重新排序关联数组。

2.6.4 使用数组定位器方法构建记分牌

数组定位器方法可用于构建记分牌。样例2.36定义了包的结构,然后用这些结构组成的队列创建记分牌。第2.8节介绍如何使用typedef创建结构体。

示例2.36带有数组方法的记分牌

示例2.36中的check_addr()函数在记分板中查找地址。方法的作用是:返回一个int队列。如果队列为空(size==0),则没有找到匹配。如果队列中有一个成员(size==1),则找到一个匹配项,check_addr()函数将删除该匹配项。如果队列有多个成员(大小为> 1),则计分板上有多个与请求地址匹配的报文。

存储数据包信息的更好选择是类,这在第5章中有描述。你可以在2.9节阅读更多关于结构的内容。

2.7 选择存储类型

以下是一些根据灵活性、内存使用、速度和排序选择正确存储类型的指导原则。这些只是经验法则,不同模拟器的结果可能不同。

2.7.1 灵活性

如果在编译时已知数组大小,则选择固定大小的数组;如果在运行时才知道数组大小,则选择动态数组。例如,可变大小的数据包可以很容易地存储在动态数组中。如果你正在编写操作数组的例程,可以考虑只使用动态数组,因为一个例程可以处理任意大小的动态数组,只要元素类型匹配:int、string等。同样,只要元素类型与queue参数匹配,就可以将任意大小的队列传递到例程中。关联数组也可以不考虑大小而传入。但是,带有固定大小数组参数的例程只接受指定长度的数组。

为非标准索引选择关联数组,例如由于值或地址是随机的,所以会被广泛分隔的值。关联数组还可以用于为内容寻址内存建模。

当元素数量在模拟过程中大幅增加或减少时,队列是存储值的一种好方法,例如保存期望值的记分牌。

2.7.2 内存使用情况

如果要减少模拟内存的使用,请使用2状态元素。您应该选择32位的倍数的数据大小,以避免浪费空间。模拟器通常将更小的数据存储在32位字中。例如,如果模拟器将每个元素放入一个32位字中,那么1024字节的数组将浪费3 / 4的内存。打包的数组也可以帮助节省内存。

对于最多容纳1000个元素的数组,您选择的数组类型不会对内存使用产生很大影响(除非这些数组有很多实例)。对于具有1000到100万个活动元素的数组,固定大小和动态数组是内存效率最高的。如果需要包含超过100万个活动元素的数组,您可能需要重新考虑您的算法。

由于添加了额外的指针,队列的访问效率比固定大小的数组或动态数组稍低一些。然而,如果你的数据集经常增长和收缩,并且你将它存储在动态内存中,你将不得不手动调用new[]来分配内存和复制。这是一个昂贵的操作,并且会抹掉使用动态内存的所有收益。

应该使用关联数组对大于几兆字节的内存进行建模。注意,由于指针开销,关联数组中的每个元素占用的内存可能是固定大小或动态大小的数倍。

2.7.3 速度

根据每个时钟周期对数组的访问次数选择数组类型。对于少量的读写操作,您可以使用任何类型,因为与DUT相比,开销较小。当您更频繁地使用数组时,数组的大小和类型很重要。

固定大小的数组和动态数组都存储在连续的内存中,因此无论数组大小如何,任何元素都可以在相同的时间内找到。

对于读写,队列具有与固定大小或动态数组几乎相同的访问时间。可以推入和弹出第一个和最后一个元素,几乎不需要任何开销。在中间插入或删除元素,需要将许多元素上下移动,以腾出空间。如果您需要将新元素插入到一个大队列中,您的测试工作台可能会变慢,因此考虑更改存储新元素的方式。

当读写关联数组时,模拟器必须在内存中搜索元素。LRM没有指定这是如何实现的,但流行的方法是散列表和树。这些数组比其他数组需要更多的计算,因此关联数组是最慢的。

2.7.4 数据访问

由于SystemVerilog可以对任何一维数组(固定大小的、动态的、关联数组加上队列)进行排序,所以您应该根据向数组中添加值的频率来选择数组类型。如果一次性接收到所有值,则选择固定大小或动态数组,这样您只需分配一次数组。如果数据缓慢地进入,那么选择一个队列,因为向头部或尾部添加新元素是非常高效的。

如果您有唯一的和不连续的值,例如{1,10,11,50},您可以将它们作为索引存储在关联数组中。使用例程

首先,next和prev可以在关联数组中搜索一个值并找到连续的值。列表是双向链接的,因此可以找到比当前值大或小的值。这两种方法都支持删除值。然而,关联数组在访问给定的索引元素时要快得多。例如,可以使用位的关联数组来保存预期的32位值。创建值后,写入该位置。当需要查看给定值是否已写入时,请使用exists函数。当处理一个元素时,

使用delete将其从关联数组中删除。

2.7.5 选择最佳的数据结构

下面是一些关于选择数据结构的建议。

  • 网络数据包。属性:固定大小,顺序访问。对于固定大小或可变大小的数据包,使用固定大小或动态数组。

  • 计分板的期望值。属性:数组大小直到运行时才知道,通过值访问,并且大小不断变化。通常,当您在模拟过程中不断添加和删除元素时,请使用队列。如果可以给每个事务一个固定的ID,比如1、2、3、…,那么可以将其用作队列的索引。如果事务中充满了随机值,则可以将它们放入队列并搜索惟一值。如果记分板可能有数百个元素,而您经常从中间插入和删除它们,那么关联数组可能会更快。如果您将事务建模为对象,则记分牌可以是句柄队列。有关类的更多信息,请参阅第5章。

  • 排序结构。如果数据以可预测的顺序出现,则使用队列;如果顺序未指定,则使用关联数组。如果记分牌从不需要搜索,那么只需将预期值存储在邮箱中,如第7.6节所示。

  • 建模非常大的内存,超过一百万个条目。如果不需要每个位置,可以使用关联数组作为稀疏内存。如果您确实计划访问每个位置,请尝试不需要那么多实时数据的不同方法。确保使用压缩到32位的2状态值来保存模拟内存。

  • 文件中的命令名或操作码。属性:将字符串转换为固定值。从文件中读取字符串,然后使用命令作为字符串索引在关联数组中查找命令或操作码。

    1. 使用typedef创建新类型

您可以使用typedef语句创建新的类型。例如,您可能有一个ALU,可以在编译时配置为使用8、16、24或32位操作数。在Verilog中,您将为操作数宽度定义一个宏,为示例2.37中所示的类型定义另一个宏。

示例2.37 Verilog中用户定义的类型宏

您实际上并不是在创建一个新类型;你只是在执行文本替换。在SystemVerilog中,您创建了一个新类型,如示例2.38所示。除了基本的uint之外,本书使用了用户定义类型使用后缀“_t”的惯例。

示例2.38 SystemVerilog中的用户定义类型

通常,SystemVerilog允许您在这些基本类型之间进行复制,而不会发出警告,如果宽度不匹配,则扩展或截断值。

请注意,parameter和typedef语句可以放在一个包中,这样它们就可以在设计和测试工作台之间共享,如2.10节所示。

您可以创建的最有用的类型之一是无符号、2状态、32位整数,如示例2.39所示。testbench中的大多数值都是正整数,比如字段长度或接收的事务数,因此使用带符号整数可能会导致问题。将uint的定义放在一个通用定义包中,这样它就可以在任何地方使用。

示例2.39 uint的定义

定义新数组类型的语法并不明显。您需要将数组下标放在新名称上。样例2.40创建了一个新类型fixed_array5_t,它是一个包含5个元素的固定数组。然后声明该类型的数组并初始化它。

样例2.40自定义数组类型

对于用户定义的类型,一个很好的用法是关联数组,它必须声明一个简单类型的索引。您可以将示例2.24更改为使用64位值,更改第一行,如示例2.41所示。

样例2.41用户定义的关联数组索引

2.8 创建用户定义的结构

Verilog最大的限制之一是缺乏数据结构。在SystemVer- ilog中,可以使用struct语句创建结构,这与c中可用的语句类似。然而,结构只是类功能的一个子集,所以在testbench中使用类,如第5章所示。正如Verilog模块结合了数据(信号)和代码(总是/初始块加上例程)一样,类结合了数据和例程,从而形成一个易于调试和重用的实体。结构只是将数据字段分组在一起。如果没有对数据进行操作的代码,您只创建了解决方案的一半。

由于结构体只是数据的集合,所以它可以被合成。如果希望在设计代码中为复杂数据类型(如像素)建模,请将其放入结构中。这也可以通过模块端口传递。最后,当您想生成受限随机数据时,请查看类。

2.8.1 创建一个结构和一个新类型

您可以将几个变量组合成一个结构。样例2.42创建了一个名为pixel的结构,它有三个无符号字节,分别为红色、绿色和蓝色。

样例2.42创建单个像素类型

前面声明的问题是它创建了这种类型的单个像素。为了能够使用端口和例程共享像素,您应该创建一个新类型,如示例2.43所示。

示例2.43像素结构体

在声明结构时使用后缀“_s”。这使得更容易发现用户定义的类型,简化了共享和重用代码的过程。

2.8.2 初始化结构

可以像数组一样将多个值赋给结构体,可以在声明中,也可以在过程赋值中。只需用撇号和大括号将值括起来,如示例2.44所示。

样例2.44初始化结构体

2.8.3 使几个类型的联合

在硬件中,寄存器中一组位的解释可能依赖于其他位的值。例如,一个处理器指令可能有许多基于操作码的布局。立即模式操作数可以在操作数字段中存储一个文字值。对于整数指令和浮点指令,这个值可能被不同的解码。样例2.45将无符号位向量b和整数i存储在同一个位置。

示例2.45使用typedef创建一个联合

在声明联合时使用后缀“_u”。

当你经常需要用几种不同的格式读写一个寄存器时,联合是很有用的。但是,不要太过火,尤其是为了节省内存。联合可以帮助从一个结构中挤出几个字节,但代价是必须创建和维护更复杂的数据结构。相反,创建一个带有判别变量的类,如8.4.4节所示。这个“类”

变量指示您拥有哪种类型的事务,以及要读、写和随机化哪些字段。如果您只需要一个值数组,加上所有的位,请使用2.2.6节中描述的打包数组

2.8.4 包装结构

SystemVerilog允许您通过使用打包的结构更多地控制位在内存中的布局。一个打包的结构被存储为一组连续的位,没有未使用的空间。示例2.43中像素的结构有三个值,所以它存储在三个长单词中,尽管它只需要三个字节。您可以使用packed关键字指定它应该被压缩到尽可能小的空间中,如示例2.46所示。

样品2.46包装结构

当底层位表示数值或试图减少内存使用时,使用打包结构。例如,您可以将几个位字段打包成一个寄存器。或者您可以将操作码和操作数字段打包在一起,生成一个包含整个处理器指令的值。

2.8.5 选择包装和未包装的结构

当您试图在已打包和未打包的结构之间进行选择时,请考虑该结构最常用的方式以及元素的对齐方式。如果您计划对结构进行聚合操作,比如复制整个结构,那么打包结构会更有效。但是,如果代码访问单个成员多于访问整个结构,则使用未打包结构。如果元素没有按字节对齐,则性能差异更大

边界、大小与典型的字节不匹配,或者有处理器使用的word指令。在封装结构中读取和写入奇数大小的元素需要昂贵的移位和掩码操作。

2.9 2.10包

在项目开始时,您需要创建新的类型和参数。例如,如果您的处理器与您公司的ABC总线通信,那么您的testbench需要定义ABC数据类型,以及指定总线宽度和时间的参数。另一个项目可能希望使用这些类型,以及XYZ总线的类型。

您可以为每个总线创建单独的文件,并在编译期间使用'include语句引入文件。但是,与每个总线相关联的每个名称必须是惟一的,即使是那些内部变量也必须是惟一的。如何组织这些类型以避免名称冲突?

SystemVerilog包允许您在模块、包、外加程序和接口之间共享声明,这将在第4章中描述。样本

2.47显示了ABC总线的包。

样品2.47包装为ABC总线

使用import语句从包中导入符号。当一个符号没有在通常的搜索路径中定义时,编译器只在导入的包中查找。在样例2.48中,如果没有同名的局部变量,则第一个import语句将使符号abc_data_width、abc_data_t和timeout可见。ABC中的变量message被模块中的变量隐藏了。

样例2.48导入包

如果您真的想看到ABC中的消息变量,请使用ABC::message。您可以使用作用域操作符::从包中导入特定的符号。

样例2.49从ABC中导入所有符号,外加XYZ中的timeout变量。

样例2.49从包中导入选定的符号

包只能看到自己内部定义的符号,或者它们导入的包。你不能有对符号的层次引用,比如信号、程序或者来自包外部的模块。可以将包看作是完全独立的,能够在需要的地方插入,没有外部依赖项。

一个包可以包含例程和类,如5.4节所示。

2.10 类型转换

SystemVerilog有几个规则来确保表达式的计算很少或不会损失准确性。例如,如果添加两个8位的值,添加的精度为9位,以避免溢出。将两个8位值相乘,SystemVerilog将计算一个16位结果。

SystemVerilog中数据类型的激增意味着您可能需要在它们之间进行转换。如果源和目标变量之间的位布局相同(例如整数和枚举类型),则在这两个值之间进行强制转换。如果位布局不同,例如字节和字的数组,使用流运算符来重新排列位,如2.12节所述。

2.10.1 静态铸造

静态强制转换操作在不检查值的情况下转换两种类型。指定目标类型、撇号和要转换的表达式,如示例2.50所示。注意,Verilog总是隐式地在类型(例如整数和实数)之间进行转换,也在不同的宽度向量之间进行转换。

使用静态类型转换int和real之间的转换

2.10.2 2.11.2 动态投

动态类型转换$cast允许检查越界值。参见

2.13.3查看枚举类型的解释和示例。

当您希望SystemVerilog使用更精确的类型时,如对单个位数组使用sum方法时,请使用静态强制转换。在从具有比目标更多值的类型(如int)转换为枚举变量时,请使用动态强制转换。

2.11 流媒体运营商

当在赋值操作的右侧使用时,流操作符<<和>>接受一个表达式、结构或数组,并将其打包到一个位流中。>>操作符从左向右流数据,而<<操作符从右向左流数据,如示例2.51所示。您还可以给出一个切片大小,用于在流之前分割源。不能将位流结果直接分配给未打包的数组。相反,使用赋值左边的流操作符将位流解包到一个未打包的数组中。

示例2.51基本流操作符

您可以使用许多连接操作符{}来执行相同的操作,但流操作符更紧凑,更易于阅读。

如果需要打包或解包数组,请使用流操作符在不同元素大小的数组之间进行转换。例如,可以将字节数组转换为单词数组。您可以使用固定大小的数组、动态数组和队列。样例2.52在队列之间进行转换,但也可用于动态数组。数组元素会根据需要自动分配。

示例2.52使用流操作符在队列之间转换

数组之间的流的一个常见错误是数组下标不匹配。数组声明中的下标[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.53中,一个结构被流到一个动态字节数组中,然后字节数组被流回该结构中。

示例2.53在结构体和带有流操作符的数组之间转换

2.12 枚举类型

枚举类型允许您创建一组相关但唯一的常量,比如状态机或操作码中的状态。在经典的Verilog中,你必须使用text mac- ros。它们的全局作用域太宽,它们的值可能在调试器中不可见。枚举创建限制为一组指定名称的强类型变量。例如,与使用8'h01或宏等文字相比,名称ADD、MOVE或row使代码更容易编写和维护。定义常量的一个较弱的选择是形参。这对于单独的值很好,但是枚举类型会自动为列表中的每个名称提供唯一的值。

最简单的枚举类型声明包含一个常量名称列表和一个或多个变量,如示例2.54所示。这将创建一个匿名枚举类型,但它不能用于除此声明中的变量之外的任何其他变量。

示例2.54一个简单的枚举类型,不推荐

建议创建一个命名枚举类型,这样您就可以声明多个相同类型的变量,特别是当这些变量被用作例程参数或模块端口时。首先创建枚举类型,然后创建该类型的变量,如示例2.55所示。您可以使用内置函数name()获得枚举变量的字符串表示形式。

示例2.55枚举类型,推荐的样式

在声明枚举类型名称时,使用后缀“_e”。

2.12.1 定义枚举值

实际值默认为int,从0开始,然后递增。您可以选择自己的枚举值。示例2.56中的代码使用默认值0表示INIT, 2表示DECODE, 3表示IDLE。

示例2.56指定枚举值

枚举常量,例如示例2.56中的INIT,遵循与变量相同的作用域规则。因此,如果在几个枚举类型中使用相同的名称(如不同状态机中的INIT),则必须在不同的作用域(如模块、程序块、包、例程或类)中声明它们。

除非指定其他类型,否则枚举类型以int存储。给枚举常量赋值时要小心,因为int的默认值是0。在样例2.57中,position被初始化为0,这不是一个合法的序数变量。这

行为不是一个工具错误——它是语言如何被指定的。因此,总是指定一个值为0的枚举常量,如示例2.58所示,只是为了捕获testbench错误。

示例2.57错误地指定枚举值

示例2.58正确指定枚举值

2.12.2 枚举类型的例程

SystemVerilog提供了几个用于逐步遍历枚举类型的函数。

  • first()返回枚举的第一个成员。

  • last()返回枚举的最后一个成员。

  • next()返回枚举的下一个元素。

  • next (N)返回N th下一个元素。

  • prev()返回枚举的前一个元素。

  • prev(N)返回N th之前的元素。

当函数next和prev到达枚举的开头或结尾时,它们会绕行。

请注意,如果使用枚举循环变量,则没有干净的方法来编写遍历枚举类型的所有成员的for循环。你得到了第一个函数的起始元素和下一个元素。当循环变量超出定义范围时,for循环结束,但下一个函数总是返回枚举内的值。如果你用的是测试电流!= current.last(),循环在使用最后一个值之前结束。如果你使用current<=current.last(),你会得到一个无限循环,因为next永远不会给你一个大于最终值的值。这类似于尝试做一个for循环,遍历值0..索引声明为bit[1:0]。这个循环永远不会退出!您可以通过在循环中使用整数变量或递增枚举变量来绕过这个限制,但如果枚举值不是连续的,这两种解决方案都可能给出非法值,如1、2、3、5、8。

你可以使用一个do…while循环来遍历所有的值,检查值何时换行,如样例2.59所示。

示例2.59逐步遍历所有枚举成员

2.12.3 与枚举类型之间的转换

枚举类型的默认类型是int (2-state)。您可以将枚举变量的值赋值给一个非枚举变量,比如一个简单的赋值int。但是,SystemVerilog不允许在不显式更改类型的情况下将整数值存储在enum中。相反,它要求您显式地强制转换值,以使您意识到您可能正在编写一个越界值。

示例2.60整数和枚举类型之间的赋值

如样例2.60所示,当作为函数调用时,$cast()试图将右边的值赋给左边的变量。如果赋值成功,$cast()返回1。如果由于越界值而赋值失败,则不进行赋值,函数返回0。如果使用$cast()作为任务,而操作失败,则SystemVerilog将打印一个错误。

您也可以使用示例中所示的类型'(val)强制转换值,但这并不执行任何类型检查,因此结果可能是越界的。例如,

在样例2.60中的静态强制转换之后,c2有了一个越界值。您应该避免使用枚举类型进行这种类型的强制转换。

2.13 常量

SystemVerilog中有几种类型的常量。Verilog创建常量的经典方法是使用文本宏。另一方面,宏具有全局作用域,可以用于位字段定义和类型定义。从消极的方面来说,宏是全局的,所以如果你只需要一个局部常量,它们可能会导致冲突。最后,宏需要'字符,这样编译器才能识别和扩展它。

Verilog参数是松散类型的,其作用域仅限于单个模块。Verilog-2001增加了类型化参数,但是它们有限的作用域使参数器不能被广泛使用。在SystemVerilog中,可以在包中声明参数,这样它们就可以跨多个模块使用。这种方法可以替换大多数作为常量使用的Verilog宏。

SystemVerilog还支持const修饰符,该修饰符允许您创建一个变量,该变量可以在声明中初始化,但不能由过程性代码编写。

示例2.61声明const变量

在样例2.61中,冒号的值在运行时初始化,当初始化

阻止进入。在下一章中,样例3.11展示了const例程参数。

2.14 字符串

如果您曾经尝试过使用Verilog reg变量来保存字符串,那么您的痛苦就结束了。SystemVerilog字符串类型包含可变长度的字符串。单个字符的类型是byte。长度为N的字符串的元素编号为0到N-1。注意,与C语言不同,字符串的末尾没有空字符,任何使用字符" \0 "的尝试都会被忽略。字符串的内存是动态分配的,因此不必担心存储字符串的空间不够用。

样例2.62显示了各种字符串操作。函数getc(N)返回位置为N的字节,而toupper返回的是字符串的大写副本,tolower返回的是小写副本。花括号{}用于连接。任务putc(M, C)将字节C写入位置M的字符串中,该字符串必须在0之间

长度由len给出。函数的作用是:从起始位置到结束提取字符。

示例2.62字符串方法

注意动态字符串是多么有用。在C等其他语言中,您必须不断创建临时字符串来保存函数的结果。在样例2.62中,使用的是Verilog-2001中的$sformatf函数,而不是$sformat。这个新函数返回一个格式化的临时字符串,如上面所示,可以直接传递给另一个例程。这使您不必声明一个临时字符串,并在格式化语句和例程调用之间传递它。未记录的函数$psprintf与$sformatf具有相同的功能,但不在LRM中,尽管大多数供应商支持这个非标准的系统函数。

有两种方法来比较字符串,但是它们的行为不同。相等操作符s1==s2,如果字符串相同则返回1,反之返回0。字符串比较函数s1.compare(s2),如果s1大于s2则返回1,如果大于s2则返回0

如果s1小于s2,则为−1。虽然这符合ANSI C strcmp()行为,但它可能不是您所期望的。

2.15 表达式的宽度

在Verilog中出现意外行为的一个主要来源是表达式的宽度。示例2.63使用四种不同的样式添加了1+1。加法A使用两个1位变量,因此精度为1+1=0。加法B使用8位精度是因为

在赋值的左侧有一个8位变量。在这种情况下,1+1=2。加法C使用一个虚拟常数来强制SystemVerilog使用2位精度。最后,除了D之外,第一个值通过转换操作符被转换为2位值,所以1+1=2。

表达式宽度取决于上下文

有一些技巧可以用来避免这个问题。首先,避免溢出丢失的情况,如a .使用临时的,如b8,具有所需的宽度。或者,您可以添加另一个值来强制最小精度,例如2'b0。最后,在SystemVerilog中,您可以将其中一个变量强制转换为所需的精度。

2.16 结论

SystemVerilog提供了许多新的数据类型和结构,这样您就可以创建高级的testbench,而不必担心位级表示。队列可以很好地创建记分牌,你需要不断地添加和删除数据。动态数组允许您在运行时选择数组大小,以获得最大的testbench灵活性。关联数组用于稀疏存储器和一些带有单个索引的记分牌。通过创建命名常量组,枚举类型使代码更易于阅读和编写。

不要仅仅使用这些结构来创建一个过程测试工作台。在第5章中探索SystemVerilog的OOP功能,学习如何在更高的抽象级别上设计代码,从而创建健壮和可重用的代码。

2.17 练习

  1. 给定以下代码示例:

    1. my_byte的取值范围是什么?

    2. my_int的十六进制值是什么?

    3. my_bit的十进制值是多少?

    4. my_short_int1的十进制值是多少?

    5. my_short_int2的十进制值是多少?

  2. 给定以下代码示例:

    按给定顺序计算下列语句,并给出每次赋值的结果

    1. my_mem [2] = my_logicmem [4];

    2. my_logic = my_logicmem [4];

    3. my_logicmem [3] = my_mem [3];

    4. my_mem [3] = my_logic;

    5. my_logic = my_logicmem [1];

    6. my_logic = my_mem [1];

    7. my_logic = my_logicmem [my_logicmem [41];

  3. 编写SystemVerilog代码:

    1. 声明一个2状态数组my_array,它包含4个12位的值

    2. 初始化my_array以便:

    * my_array[0] = 12'h012

    * my_array[1] = 12'h345

    * my_array[2] = 12'h678

    my_array [3] = 12 'h9ab

  4. 遍历my_array并打印出每个12位元素的位[5:4]

    • 使用for循环

    • 用foreach环

  1. 声明一个5乘31的多维未打包数组my_array1。未打包数组的每个元素都保存一个4种状态的值。

    1. 下列哪个赋值语句是合法且不越界的?

    * my_array1[4][30] = 1'b1;

    * my_array1[29][4] = 1'b1;

    * my_array1[4] = 32'b1;

    1. 在合法赋值完成后绘制my_array1。
  1. 声明一个5乘31的多维打包数组my_array2。已打包数组的每个元素都保存一个2状态值。

    1. 下列哪个赋值语句是合法且不越界的?

    * my_array2[4][30] = 1'b1;

    * my_array2[29][4] = 1'b1;

    * my_array2[3] = 32'b1;

    1. 在赋值语句完成后绘制my_array2。
  2. 给定以下代码,确定将显示什么。

  3. 为以下问题编写代码。

    1. 为一个字宽为24位,地址空间为220字的处理器创建一个关联数组。假设重置时PC从地址0开始。程序空间从0400开始。ISR为最大地址。

    2. 请按照以下说明填充内存:

      • 24 'ha50400;//主代码跳转到位置0400

      • 24 'h123456;//指令1位于位置0400

      • 24 'h789abc;//指令2位于位置0401

      • 24 'h0f1e2d;// ISR = Return from interrupt

    3. 输出数组中的元素和元素数量。

  4. 为以下需求创建SystemVerilog代码

    1. 创建一个3字节队列,用2、−1和127初始化

    2. 输出以小数点为基数的队列的和

    3. 打印出队列中的最大值和最小值

    4. 对队列中的所有值进行排序并打印出结果队列

    5. 打印出队列中任何负值的索引

    6. 打印出队列中的正数

    7. 对队列中的所有值进行反向排序并打印出结果队列

  5. 定义一个用户定义的7位类型,并使用新类型将下列数据包的字段封装在一个结构中。最后,分配头部到7'h5A。

    27 21 20 14 13 7 6 0
    

header

cmd

data

crc

  1. 为以下需求创建SystemVerilog代码

    1. 创建一个4位的用户定义类型nibble

    2. 创建一个真实的变量r,并将其初始化为4.33

    3. 创建一个简短的int变量i_pack

    4. 创建一个未打包的数组k,包含用户定义类型nibble的4个元素,并将其初始化为4'h0、4' hf、4' he和4' hd

    5. 打印出k

    6. 以位为基础,从右向左将k流到i_pack中,并将其打印出来

    7. 以一点的方式从右到左将k流到i_pack中并打印出来

    8. 类型转换实r为一个啃,赋值给k[0],并打印出k

  2. ALU的操作码如表2.1所示。

表2.1 ALU操作码

操作码 编码

Add: A + B 2 'b00

Sub: A−B 2 'b01

位操作转化: 2 'b10

减少或:B 2 'b11

编写一个执行以下任务的测试平台。

  1. 创建操作码的枚举类型:opcode_e

  2. 创建一个opcode_e类型的变量opcode

  3. 每10ns循环遍历变量opcode的所有值

  4. 用一个2位输入操作码实例化ALU

results matching ""

    No results matching ""