一 数据类型
内建数据类型
verilog中,对于触发器,锁存器用reg类型,对于reg,会被综合成register,latch
wire,做连接
sv中,logic可以被综合为reg或wire,logic如果在验证环境,只会作为单纯的变量进行赋值操作。
verilog & sv区别:
verilog作为硬件描述语言,倾向于设计人员自身懂得所描述的电路中哪些变量应该实现为reg或是wire,但不利于后端综合工具
sv侧重于验证语言,引入logic只会作为单纯的变量进行赋值操作,这些变量只属于软件环境构建
bit是二值逻辑:0 1
logic是四值逻辑:0 1 X Z
Q:为什么有了四值逻辑还要引入二值逻辑?
A:SV在开始做设计的时候,期望将硬件和软件分开,四值在硬件设计,二值在软件中
四值逻辑:integer(32),logic,reg,net-type
二值逻辑:byte(8bit),shortint,int(32),longint,bit
有符号类型: byte,shortint,int,longint,integer
无符号类型:bit, logic, reg, net-type(wire/tri)
note:用最高位来表征有无符号
在变量运算中,应该尽量避免两种不一致的变量操作
将有符号变量转换为无符号:unsigned' 属于静态转换
动态转换 $cast(tgt, src)
对比:静态转换在编译的时候做检查,动态转换在仿真的时候做检查
静态转换和动态转换均需要操作符号或者系统函数介入,统称为显式转换
而不需要进行转换的一些操作称为隐式转换
note:不同数据类型进行操作时应注意变量的:逻辑数据类型、符号类型、矢量位宽
软件常用类型【定宽数组】
数组声明
int lo_hi[0:15]; //16 ints [0] .. [15]
int c_style[16]; //16 ints [0] .. [15]
多维数组声明和使用
int arr2 [0:7][0:3]; // 完整声明
int arr3 [8][4]; //紧凑声明
arr2[7][3] = 1; //设置最后一个元素
初始化和赋值
int ascend[4] = `{0, 1, 2, 3}; //对四个元素初始化
int descend[5];
descend[0:2] = `{5, 6, 7} //片段赋值操作
ascend = `{4{8}} //四个值全部为8
descend = `{9, 8, default:-1 } //{9,8,-1,-1,-1}
存储空间考量
从实际硬件容量的角度出发,哪种方式存储方式更小?
bit [3][7:0] b_pack; //合并型
bit [7:0] b_unpack[3]; //非合并
b_unpack 实际占用3个WORD存储空间,但是b_pack则只会占据一个WORD存储空间
note:当左边也有维度,右边也有维度声明时,先考虑右边维度
对于unpack的数组,初始化和赋值“ ` ”要存在
Q:当b_pack和b_unpack用logic存储时,实际占用的大小为:
A:b_pack有24bit,用logic 4值表示时,每个需要2bit,i.e. 48bit =2WORD
b_unpack为3 *8,8位连续位,即3*16,其中16bit可以被一个WORD存储,则需要3WORD
note:对于unpack的数组做初始化要有“ ` ”
for和foreach循环
initial begin
bit [31:0] src[5], dst[5];
for (int i=0; i< $size(src); i++) //$size(src,default =1)
src[i] = i;
foreach (dst[j]) begin
dst[j] = src[j]*2
end
复制和比较 “==” / “!=”
软件常用类型【动态数组】
定宽数组类型的宽度在编译时就确定了,但是如果在程序运行时再确定数据的宽度需要使用【动态数组】
动态数组用的是非合并的存储方式,合并数组和非合并数组之间不能直接赋值
特点:可以在仿真运行时灵活调解数组的大小即存储量
动态数组一开始声明时,需要利用“[ ]”来声明,而此时数组是空的,即0容量,其后使用new[ ]来分配空间,方括号中传递数组的宽度
此外,也可以在调用new[ ]时将数组名一并传递,将已有数组的值复制到新数组中
d2和dyn是两个地址空间,integer默认值是X
删除方法:也可以dyn = new[0] / dyn = `{ }
【队列】
- 结合了链表和数组的优点,可以在它的任何地方添加或删除元素,并且通过索引实现对任一元素的访问
- 队列的声明是 [$] 队列元素的标号从0到$
- 队列不需要new[ ]去创建空间(new[]只对动态数组使用),只需要使用队列的方法为其增减元素,一开始空间为0
- 队列的一个简单使用是通过自带的push_back( )和pop_front( )结合来实现FIFO的用法
note:数组中用size( ),mailbox中用num( )
【关联数组】
关联数组中index不是连续的,为此尽可能不要用for循环
如果找不到第一个索引,说明第一个为空
【结构体】struct
note:verilog中没有数据结构,sv中可以使用struct语句创建结构
只是一个数据的结合,通常是将若干相关变量组合到一个结构体中
用来创建一个新的类型 typedef,为此pixel_s是类型
第一行的pixel在这里是变量名
typedef在这里也可以替换为define,区别在于typedef创建的是类型,define是变量名
note:数据是非合并型,采用`{ }方式,如果结构体用合并型,只需struct packed {bit [7:0] r, g, b} pixel
【枚举类型】
INIT,DECODE,IDLE默认是0,1,2
note:枚举类型可以赋值给整型,INT = enum, 但是整型不能直接赋值给enum
【字符串】
字符串默认" " ,与null不同,null有且只用到句柄(对象)中,字符串中没有null
note:verilog中没有str,sv中str没有"\0"
数组的大小用.size(), str用 .len() - 1
二 过程块和方法
硬件过程块 initial和always
module/endmodule,interface/endinterface 可以被视为硬件世界
program/endprogram,class/endclass可以被视为软件世界
- always是为了描述硬件的行为
- always中的@(event)敏感列表是为了模拟硬件信号的触发行为
- always过程块是用来描述硬件时序电路和组合电路的正确打开方式,因此只可以在module或者interface中使用
note:不可以在always中初始化变量,可以在always中复位,而初始化变量是在软件操作过程中,即可以在initial中
- initial只执行一次
- initial和always无法被延迟执行,在仿真一开始他们就会并行执行
- initial本身不可综合,对于描述电路没有任何帮助,不应该存在于硬件设计代码中
- initial是为了测试而生的
- initial可以在module,interface和program中使用
note:sv中的class不允许出现initial和always
软件方法 函数func
- 可以在参数列表中指定输入参数(input),输出参数(output),输入输出参数(inout),或引用参数(ref)
- 可以设置返回值或不设置返回值(void)
- 默认数据类型是logic,eg input[7: 0] addr
- 可以传参数也可以传数组(作为形参)
- 可以有返回值也可以没有返回值(void function())
- 在使用ref时,为了保护数据对象只能被读取,不能被写入,可以通过const来限定ref的声明
- 声明参数时,默认方向是input
软件方法 任务task
- task无法通过return添加返回值
- task可以置入耗时语句,包括@event, wait event, #delay
- function不可以调用task,task可以调用function
变量生命周期
分为automatic(动态,局部变量)和static(静态,全局变量)
- func/task中的临时变量,在方法调用结束后,自动释放
- 如果数据变量被声明为automatic,进入该进程/方法后,automatic变量会被创建,离开该线程/方法后会被销毁
- 对于automatic方法,其内部所有变量内部默认也是automatic
- 对于static方法,其内部所有变量内部默认也是static
note:没有显式注明动态还是静态变量的情况下,默认是static
- module,program,interface,task和func之外的变量拥有静态的生命周期,即存在与整个仿真阶段
- class中默认是automatic
- 在module, interface和program内部声明,且在task,process或者func外部声明的变量也是static
三 设计的例化和连接
例化:
硬件和软件的连接有且只能通过interface,硬件和硬件的连接可以通过端口,也可以通过interface