本文
芯片验证系列文章主要分为三部分:验证工程师与芯片验证、SV学习笔记、UVM学习笔记。此为 SV学习笔记 部分第一篇,主要介绍数据类型、过程块、方法(函数与任务)、设计例化和连接。
版本 | 说明 |
---|---|
0.1 | 初版发布 |
参考
名称 | 作者 | 来源 |
---|---|---|
《芯片验证漫游指南》 | 刘斌 | 书籍 |
《SystemVerilog验证》 | 克里斯.斯皮尔 | 书籍 |
专业术语与缩略语
缩写 | 全称 | 说明 |
---|---|---|
SV | SystemVerilog | 基于Verilog扩展而来的,为验证而生的语言 |
数据类型
內建数据类型
-
四状态与双状态 :
- 四状态指0、1、X、Z,包括logic、integer、 reg、 wire。
- 双状态指0、1,包括bit、byte、 shortint、int、longint。
-
有符号与无符号 :
- 有符号:byte、shortint、int、longint、integer。
- 无符号:bit、logic、reg、wire。
-
关于数据类型使用的几个注意点 :
- SV中虽然支持reg和wire,但对于 验证平台要尽量使用logic ,并且建议采样RTL信号时变量要使用logic类型。
- 实际工作中 使用最多的是logic和bit ,一般 需要计数和比较大小时会使用byte或int 。
- 尽量 避免两种不同数据类型变量进行操作 ,包括 有无符号、四状态双状态、不同位宽 ,如必须进行操作,请先转换为同一类型。
-
关于数据类型转换的几个注意点 :
- 四状态转换为双状态时, x和z转换为0 。
- 多位数据赋值给少位数据,则 高位被截取忽略 ;少位数据赋值给多位数据,双状态类型的 高位赋值为0 ,四状态类型的 高位赋值为x 。(以上指的是无符号类型)
- 有符号变量转无符号变量,直接赋值的话会将 原始比特数据 赋给无符号变量,但其原符号位失去含义;使用转换语句转换的话,会将有符号变量 取模 赋值给无符号变量。
- 总之,还是尽量避免数据类型间的转换,数据类型间的转换是容易出错的地方,需要格外注意。
-
数据类型转换操作 :
|
|
定宽数组
- 数组声明 :
|
|
- 数组的初始化和赋值 :
|
|
- 存储空间 :
|
|
- 数组操作之for和foreach循环 :
|
|
- 数组操作之复制和比较 :
|
|
动态数组
- 定宽数组类型宽度编译时已经确定,若在程序运行时确定数组宽度就要使用 动态数组 。
- 动态数组特点就是仿真运行时灵活调节数组的大小,也就是存储量。
- 动态数组开始时使用“[]”来声明,此时数组为空,其后使用“new[]”来分配空间,方括号中传递数组宽度。
- 调用“new[]”时也可以将数组名一并传递,将已有数组的值复制到新的数组中。
|
|
队列
- 队列结合了链表和数组的优点,可以在任何地方添加和删除元素,并且通过索引实现对任一元素的访问。
- 队列的声明是使用美元符号的下标:[$],队列元素标号从0到$。
- 队列不需要new[]去创建空间,只需要使用队列的方法为其增减元素,队列初始空间为零。
- 队列的简单使用是通过 push_back()和pop_front() 的结合来实现FIFO的用法。
|
|
关联数组
- 如果需要一个超大容量存储空间,而有相当部分数据不会被存储和访问,不管使用定宽数组还是动态数组,都会造成存储的浪费,这时候需要使用关联数组。
- 关联数组可以保存稀疏矩阵元素,当你对一个非常大的地址空间进行寻址时,该数组 只为写入的元素分配空间 ,所以关联数组需要的空间远小于定宽或动态数组。
- 此外关联数组的灵活应用,在其他高级语言中都有类似的存储结构,比如Perl语言中称为哈希(Hash),Python中称为词典(Dictionary),可以灵活赋予key和value。
|
|
结构体
- sv中可以使用struct语句创建结构,与c语言类似。
- sv中struct功能较少,只可以定义一个 数据的集合 ,也就是将若干相关变量组合到一个struct结构定义中。
- 通过 使用typedef和struct,可以定义新的数据类型 ,可利用新的数据类型声明变量。
|
|
- 关于赋值时什么时候使用单引号:
- 合并型存储的不需要使用单引号,就好比数据的拼接,队列是合并型存储的。
- 非合并型存储需要使用单引号,如数组和结构体。
枚举类型
- 规范的操作码和指令有利于代码的编写和维护,如ADD、WRITE、IDEL等。
- 枚举类型enum经常 和typedef搭配使用 ,由此便于用户自定义枚举类型的共享使用。
- 枚举类型 保证避免一些非期望值的出现 ,增加代码可维护性和降低设计风险。
|
|
字符串
- verilog语言中是不存在字符串的,而sv中添加了字符串string类型。
- 所有相关的字符串处理,都使用string来保存和处理。
- 字符串处理相关的格式化函数可以 使用$sformatf() ,如果只是打印输出,可以直接使用$display()。
|
|
过程块
- 过程快有两种:initial和always。
- initial是 不可综合的 ,为验证而生,always是 可综合的 ,代表硬件电路。
- always是 硬件行为 ,可综合,使用时需要 区分时序电路描述和组合电路描述 。
- initial是 软件行为 , 块内语句 顺序执行,且只执行一次 。
- initial块和always块之间,以及不同initial块,不同always块,在 仿真一开始都是同时执行 的。
- 在verilog时代,所有的测试都放在initial块中,并且为了便于统一管理,建议 放在同一个initial块中 。
- module、interface可视为 硬件域 ,program、class可视为 软件域 ,区分硬件域和软件域对理解initial和always很有帮助。
- initial块可以放在module、interface和program中;always块只能放在module、interface中。
- 对于过程块,使用 begin…end 将其作用域包住,对于控制语句和循环语句,同样适用。
方法(函数与任务)
函数function
- 可以在参数列表指定输入参数(input)、输出参数(output)、输入输出参数(inout)或者引用参数(ref),如果不指明默认为input。
- 可以有返回值,也可以无返回值(void)。
- 函数其他属性:
- 默认数据类型为logic。
- 数组可以作为形式参数传递。
- function可以返回或不返回结果,返回结果需要使用关键字return,不返回需要声明为void function。
- 只有数据变量可以在形式参数列表被声明为ref类型,而线网类型则不能声明为ref类型。
- 使用ref时,有时为了保护参数对象只被读取不被修改,可以通过const的方式限定ref声明的参数。
- 在声明参数时,可以设置默认值(input a=10),同时如果在调用时省略参数的传递,则函数中使用默认值。
|
|
任务task
任务相比函数更加灵活,且有以下不同点:
- task无法通过return返回结果(也无需加void),只能通过input、output、inout或ref的参数来返回。
- task内 可以使用耗时语句 ,而function不能。常见的耗时语句如: @event、wait event、#delay 等。
|
|
使用建议
- 初学者傻瓜式用法,可以 全部采用task来定义方法 ,因为它可以内置耗时语句,也可不以内置耗时语句。
- 经验者要区分两种方法, 非耗时方法使用function,耗时方法使用task ,也就是function中完成纯粹的逻辑运算,而task更多完成需要耗时的信号采样或者驱动等场景。
- 调用function:在function和task内均可以调用其他function;用task,如果被调用task内使用了耗时语句,只能在task调用。
变量的声明周期
- sv中数据的生命周期分为 动态(automatic)和静态(static) 。
- 局部变量 的生命周期与其所在域共存亡,也就是在function/task中的临时变量, 在其被调用结束后,临时变量的生命周期也将终结 。
- 全局变量 在程序执行 开始到结束一直存在 。
- 如果数据变量被声明为automatic,那么在进入该进程/方法后,automatic变量会被创建,离开该进程/方法后,automatic变量被销毁。而static在仿真开始时被创建,而在进程/方法执行过程中,不会被销毁,且可以 被多个进程和方法所共享 。
- module内全部是静态变量,代表真实的电路结构。
- 对于automatic方法,其内部所有变量默认也是automatic。
- 对于static方法,其内部所有变量默认也是static。
- 对于static变量, 声明时应该对其做初始化 ,而初始化只会伴随它的生命周期执行一次,不会随着方法调用而多次初始化。
- 在module、program、interface声明的变量,以及其他在task/function之外声明的变量,默认是静态变量,存在于是整个仿真阶段。
设计例化和连接
模块定义
|
|
模块例化
|
|
模块连接
模块连接就是将硬件电路在测试平台进行例化,传统的verilog验证方法,在initial过程块产生激励,驱动硬件电路完成仿真。
文章原创,可能存在部分错误,欢迎指正,联系邮箱 cao_arvin@163.com。