数字硬件建模SystemVerilog(十三)-枚举数据类型
上一节介绍了已经被淘汰的$unit声明空间,今天我们来看看一种重要的数据类型-枚举数据类型。
枚举数据类型提供了一种声明变量的方法,该变量可以包含有效值的特定列表。每个值都与一个标签(确定的用户自定义名宇)相关联。枚举变量用enum关键字声明,后面是用大括号({})括起来的逗号分隔的标签列表。
在下面的示例中,变量rgb的值可以是RED GREEN BLUE
枚举列表中的标签是常量,类似于localparam常量。标签可以是任何名称。本系列使用大写字母作为常量的惯例。
枚举数据类型声明语法
枚举数据类型有一个底层数据类型,称为基类型,可以是任何SystemVerilog内置数据类型或用户自定义类型。枚举列表中的每个标签都有一个与该标签关联的逻辑值。
SystemVerilog提供了两种用于声明枚举数据类型的样式:隐式样式和显式样式。
隐式样式枚举声明
隐式样式枚举声明使用基类型和标签值的默认值。默认的基本类型是int。标签的默认值是列表中的第一个标签的值为0,并且每个后续标签的值递增一。
在以下隐式样式枚举声明中:
enum {WAITE, LOAD, READY} states_e;
states_e是int数据类型的变量,是32位有符号数据类型。这意味着枚举列表最多可以有2147483648(2^(32-1))个标签。
列表中的第一个标签WAITE的值为0,LOAD为l,READY为2。(标签WAITE故意在末尾拼写为“E”,以避免与SystemVerilog中保留的关键字wait产生混淆或冲突。)
这些默认设置很少适用于硬件建模。基类型int是2-state类型,这意味着在仿真期间导致X的任何设计问题都不能反映在枚举变量中。基类型int的宽度为32位,通常比所表示的硬件所需的向量大得多。标签值(如0、1和2)不能代表很多其他类型的硬件设计中使用的编码,例如独热码值、格雷码或约翰逊计数。
显式样式枚举声明
显式样式枚举声明指定基类型和标签值。以下声明表示使用一种独热编码的3位宽状态变量:
显式样式枚举声明强制使用了几个语法规则,可以帮助防止编码错误:
基类型的向量宽度和标签值的显式宽度必须相同。允许使用大小不一的文字值(例如WAITE = 1)
每个标签的值必须是唯一的;两个标签不能具有相同的值。
标签的数量不能超过基本类型的向量宽度所能代表的数量。
无需指定枚举列表中每个标签的值。
如果未指定值,则该值将从上一个标签增加1。在下一个例子中,标签A显式地给出了一个值l,B自动给出了递增为值2,C给出了递增的值3。D被明确定义为具有13的值,E和F分别被赋予14和15的递增值。
如果两个标签的值相同,则会导致错误。以下示例将产生一个错误,因为c和D的值相同,都为3:
最佳实践指南4-3 |
---|
在RTL模型中使用显式样式枚举数据类型声明,在RTL模型中,基类型和标签值是指定的,而不是推断的。 |
指定基本类型和标签值有几个优点:它记录了设计工程师的意图;它可以更准确地仿真门级行为,并允许更准确的RTL到门级逻辑等价性检查。
自定义和匿名枚举数据类型
可以使用typedef将枚举数据类型声明为用户自定义类型,这为使用相同的枚举值集声明多个变量或网络提供了一种便捷的方法。
使用typedef声明的枚举数据类型称为自定义枚举数据类型。如果未使用typedef,则枚举数据类型称为匿名枚举数据类型。
枚举数据类型标签序列
有两种快捷方式可以指定枚举数据类型列表中具有相似名称的多个标签。
COUNT_[4] 快捷方式将生成四个标签,分别为COUNT_0、COUNT_1、COUNT_2和COUNT_3。与COUNT_0关联的值将默认为0,随后每个标签的值将增加一。
第二个快捷方式:指定一系列标签。
COUNT_[8:11]简写符号将生成四个标签,分别为COUNT_8、COUNT_9、COUNT_10和COUNT_11。与COUNT_8关联的值被明确定义为8,后续标签的值将增加1。
如果范围中的第一个值小于第二个值,如在COUNT_[8:11]中,则序列将从第一个数字递增到最后一个数字。如果范围内的第一个值大于第二个值,如COUNT_[11: 8]中所示,序列将从第一个数字递减到最后一个数字。
枚举数据类型标签作用域
枚举数据类型列表中的标签在声明和使用标签的范围内必须是唯一的。可以包含枚举数据类型声明的RTL建模范围是模块、接口、包、begin-end块、任务、函数和$unit声明空间。
以下代码片段将导致错误,因为枚举标签 GO在同一模块范围内使用两次:
可以通过将至少一个枚举数据类型声明放在具有自己的名称范围的begin-end块中来纠正上例中的错误。
如上图所示为begin-end块命名不是必需的,但有助于记录代码的可读性和维护性。
从包中导入枚举数据类型
自定义枚举数据类型可以在一个包中定义,它允许多个设计块和验证代码使用相同的定义。
笔记 |
---|
枚举数据类型定义的显式导入不会导入该定义中使用的标签。 |
使用包的通配符导入是解决此限制的最简单方法 。通配符导入使包中的所有内容都可用。
从包导入自定义枚举数据类型定义时,只导入自定义名称。枚举列表中的值标签不会自动导入,并在导入枚举数据类型名称的名称空间中显示。下面的代码片段将不起作用。
为了同时导入枚举数据类型标签,必须显式导入每个标签,或者必须通配符导入包-通配符导入将使枚举数据类型名称和枚举标签在import语句的范围内可见。下面的部分示例显示了通配符导入的使用。
从多个包进行通配符导入时必须小心。如果在多个包中定义了标识符(名称),并且两个包都使用通配符导入,则会发生编译或细化错误。对于这种情况,要使用的标识符必须显式导入或直接导入。SV包定义中讨论了如何使用多个软件包。
枚举数据类型分配规则
大多数SystemVerilog变量类型都是弱类型的,这意味着任何数据类型的值都可以分配给变量,该值将使用SystemVerilog标准中指定的转换规则转换为变量类型。
枚举类型不在 SV的这个一般原则内。枚举数据类型变量是半强类型的,这意味着只能为该变量指定特定的数据类型。
只能为枚举数据类型变量赋值:
枚举数据类型列表中的标签。
同一类型的另一个枚举数据类型变量。也就是说,这两个变量都是使用相同的自定义或匿名枚举数据类型定义声明的。
转换为自定义枚举数据类型的值,
使用以下定义和枚举变量举例说明这些规则:
如下所述,state 和 next_state分配枚举变量既是合法的也是非法的:
笔记 |
---|
枚举数据类型的强类型规则仅适用于对枚举变量的赋值。存储在枚举变量中的值只是一个值,在表达式(如比较和数学运算)中不受限制地使用。 |
对枚举数据类型值的操作。对枚举数据类型变量执行操作时,枚举变量的值将转换为枚举数据类型定义的基类型。操作的结果不再是枚举数据类型,结果可以分配给常规的、弱类型的变量,但不能分配回枚举变量。
logic [2:0] temp; // 非枚举变量 temp = next_state + 1; // 合规的:temp是弱类型的 state = next_state + 1; // 非法的:next_state + 1不是枚举表达式 state++; // 非法的:++的结果不是枚举表达式 state += next_state; // 非法的:+=的结果不是枚举表达式
将表达式强制转换为枚举数据类型。任何值都可以强制转换为自定义枚举数据类型,然后分配给该枚举数据类型的变量,即使该值与枚举定义的某个标签不匹配,
在RTL建模中,有时需要将非枚举表达式强制转换为枚举数据类型。然而,使用cast运算符(后面将详细讨论)时必须小心。将一个值强制放入不在枚举列表中的枚举变量可能会导致错误行为;无论是在仿真还是在综合中。使用强制转换会给设计工程师带来负担,因为要确保枚举变量中只强制输入有效值。这与弱类型的正则变量没有什么不同,设计工程师需要确保指定的值是有效的。
SystemVerilog还有一个cast系统功能,可以自动验证cast操作的结果。不幸的是,对于RTL设计人员来说,cast不受一些主要综合编译器的支持,cast在验证测试台上很有用,但不被认为是可综合的结构体。
枚举类型的专用系统任务和方法
枚举数据类型有几个内置函数,称为方法methods,用于遍历枚举数据类型列表中的值。这些方法会自动处理枚举数据类型的半强类型性质,这样做很容易,比如递增到枚举数据类型列表中的下一个值,以及跳到列表的开头或结尾。使用这些方法,不需要知道标签名称。
笔记 |
---|
在撰写本文时,一些综合编译器支持枚举数据类型方法,但并非所有综合编译器都普遍支持。 |
枚举数据类型方法对硬件行为建模的用处有限。它们只能通过赋值语句实现的快捷方式。由于枚举数据类型方法的综合限制,本文仅简要介绍了这些方法,并给出了一个简单的示例。
调用枚举方法的方法是将方法名附加到枚举数据类型变量名的末尾,并以句点(.)作为分隔符。这些方法是:
<枚举变量名>.first-返回指定变量的枚举列表中第一个成员的值。
<枚举变量名>.last-返回枚举列表中最后一个成员的值。
<枚举变量名>.next(N)-返回枚举列表中下—个成员的值。可以用一个整数值作为 next 的参数。在这种情况下, 从枚举变量的当前位置算起, 返回后面第 N 个成员的值。如果到达了枚举列表的末尾, 则会返回到列表的开头。如果枚举变量的当前值不在枚举列表中, 则返回列表中第一个成员的值。
<枚举变量名>.prev(N))-返回枚举列表中前一个成员的值。同 ne*t 的方法一样.可以给 prev 指定一个整数参数 。在这种情况下, 从枚举变量的当前位S算起, 返回前面第 N 个成员的值。如果到达枚举列表的开头, 则会返冋到列表的末尾。如果枚举变量 当前值不在枚举列表中 , 则返回列表中敁后一个成员的值。
<枚举变量名>.num-返回变量枚举列表中的标签个数。
<枚举变量名>.name-返回枚举变里中代表这个值的字符串。如果这个值不在枚举变M列表中, 则返回一个空字符串。
打印枚举数据类型。枚举数据类型值可以打印为标签的实际值,也可以打印为标签的名称。直接打印枚举数据类型变量将打印枚举数据类型变量的当前实际逻辑值。使用name方法可以打印表示当前值而不是实际值的标签。
举一个使用枚举方法的例子,这个例子演示了如何使用其中一些枚举数据类型方法来建模状态机序列器。该模型是一个状态机,可以设置或清除数据同步标志。如果数据匹配输入在至少8个连续时钟周期内为真,则设置数据同步标志;如果数据匹配输入在多个连续时钟周期内为假,则清除数据同步标志,清除数据同步标志所需的连续假数据匹配数取决于有多少连续周期数据匹配为真,
图4-1显示了该状态机的状态流。状态机可以递增或递减的计数器。计数器统计已发生的连续数据匹配数,最多为16。请注意,对于大多数状态,计数器要么递增1,要么递减2。next和prev枚举数据类型方法可以非常简洁地仿真这种递增或递减行为,但某些综合编译器可能不支持这种方法。
图4-1:置信计数器(confidence counter))状态机的状态图 示例4-5:对置信计数器状态机使用枚举数据类型方法
// // Book, "RTL Modeling with SystemVerilog for ASIC and FPGA Design" // by Stuart Sutherland // // State machine model for a confidence counter modeled using // enumerated type methods. // // Copyright 2016, Stuart Sutherland. All rights reserved. // // Version 1.0 // `begin_keywords "1800-2012" // use SystemVerilog-2012 keywords module confidence_counter (input logic data_matches, compare_en, rstN, clk, output logic data_synched ); timeunit 1ns/1ns; typedef enum logic [3:0] {COUNT[0:15]} states_enum_t; states_enum_t CurState, NextState; // sequential block always_ff @(posedge clk, negedge rstN) if (!rstN) CurState <= COUNT0; else CurState <= NextState; // next state combination logic block always_comb begin if (!compare_en) NextState = CurState; // not comparing (no state change) else if (data_matches) // compare_en && data_matches case (CurState) COUNT15 : ; // can't increment past 15 default: NextState = CurState.next; // increment by 1 endcase else // compare_en && !data_matches case (CurState) COUNT0 : ; // can't decrement below 0 COUNT1 : NextState = CurState.prev(1); // decrement by 1 default: NextState = CurState.prev(2); // decrement by 2 endcase end // register output block always_ff @(posedge clk, negedge rstN) if (!rstN) data_synched <= 0; else begin if (CurState == COUNT8) data_synched <= 1; else if (CurState == COUNT0) data_synched <= 0; end endmodule: confidence_counter `end_keywords
没有枚举数据类型的传统Verilog编码风格
Verilog语言在成为SystemVerilog之前没有枚举数据类型。要为数据值创建标签,必须定义一个parameter或localparam常量来表示每个值,并为该常量指定一个值。或者,可以使用'define宏定义一组宏名称,每个名称都有特定的值。
使用parameter创建标签的一些示例如下:
请注意,在使用parameters时,state和nex_state变量是reg类型的通用变量,而不是枚举变量。这些通用变量是弱类型的,这意味着任何值都可以分配给变量。使用弱类型的赋值规则,以下赋值语句是合法赋值,但属于功能性错误:
这种编码错误可能是枚举数据类型变量的语法错误。使用传统的Verilog风格的参数和通用变量类型并不能防止像这样的意外编码错误。
SystemVerilog(十二)-$unit声明空间
SystemVerilog(十一)-SystemVerilog 包
原文标题:SystemVerilog(十三)-枚举数据类型
文章出处:【微信公众号:OpenFPGA】欢迎添加关注!文章转载请注明出处。
全部0条评论
快来发表一下你的评论吧 !