Verilog 基础¶
约 2653 个字 188 行代码 预计阅读时间 16 分钟
Note
Verilog 代码与同学们之前学习的 C 语言有很多相似之处,比如自顶向下设计思想、模块化编程、循环语句、条件语句、多路分支语句等,但是又与 C 语言大不相同,因为这是一个硬件描述语言,最后所有代码都将转化为各种门与电路的相连,无论何时同学们都应记住这一点,将 Verilog 代码当作真实的硬件电路去设计,而不是将 C 语言编程的那一套照搬到 Verilog 编程中去。
标识符 与 关键字¶
标识符可以是任意一组字母、数字、$ 符号和 _ (下划线)符号的合,但标识符的 第一个字符必须是字母或者下划线,不能以数字或者美元符开始 ,标识符也区分大小写。
常量¶
Verilog HDL 中有下列四种基本的值来表示硬件电路中的电平逻辑:
- 0:逻辑 0 或 "假"
- 1:逻辑 1 或 "真"
- x 或 X:未知
- z 或 Z:高阻
整型数的定义¶
- 如果定义的长度比为常量指定的长度长,通常在左边填0 补位。但是如果数最左边一位为x 或z ,就相应地用x 或z 在左边补位。例如
- 10'b10 左边添0占位, 0000000010
- 10'bx0x1 左边添x占位, x x x x x x _ x 0 x 1
- 如果长度定义得更小,那么最左边的位相应地被截断。例如
- 3 'b1001 _ 0011 与 3 'b011 相等
- 5 'H0FFF 与5 'H1F 相等 //注意,位宽5代表5位二进制,而不是五位16进制!!!
字符串型的定义¶
一个字符用8为ASCII值表示
数据类型¶
在我们的正常使用了,可以无脑定义变量类型为 wire
,如果报错了再改为 reg
。
线网类型 net type¶
线网类型用于对结构化器件之间的物理连线的建模。如器件的管脚,内部器件如与门的输出等。
由于线网类型代表的是物理连接线,因此它不存贮逻辑值。必须由器件所驱动。通常由assign进行赋值。如 assign A = B ^ C;
当一个 wire 类型的信号没有被驱动时,缺省值为 Z(高阻)。
信号没有定义数据类型时,缺省为 wire 类型。
此外,对wire
的赋值不能在always
/ initial
语句中。
寄存器类型 reg type¶
reg 是最常用的寄存器类型,寄存器类型通常用于对存储单元的描述,如D型触发器、 ROM等。存储器类型的信号当在某种触发机制下分配了一个值,在分配下一个值之时保留原值。但必须注意的是, reg 类型的变量,不一定是存储单元,如在always 语句中进行描述的必须用reg 类型的变量。
- 寄存器类型的值可取负数,但若该变量用于表达式的运算中,则按无符号类型处理,如:
则 A 的二进制位 1111 , 在运算中 , A 总按无符号数 15 来看待
- 用寄存器数组类型来建立存储器的模型,如对2个8位的RAM的建模如下:
对向量的一些操作¶
参数¶
参数用来表示常量,用关键字 parameter 声明,只能赋值一次。例如:
时间¶
Verilog 使用特殊的时间寄存器 time 型变量,对仿真时间进行保存。其宽度一般为 64 bit,通过调用系统函数 $time 获取当前仿真时间。
运算符和表达式¶
算术运算符¶
- 需要注意的是,在计算过程中,结果的位宽可能会扩展,因此需要十分注意结果的位宽是否足够,否则可能存在高位被丢弃的情况。
- 算术表达式结果的长度由最长的操作数决定。在赋值语句下,算术操作结果的长度由操作符左端目标长度决定。
- 在Verilog HDL 中定义了如下规则:表达式中的所有中间结果应取最大操作数的长度(赋值时,此规则也包括左端目标)。
关系运算符与逻辑运算符¶
按位运算符¶
按位操作符会对两个操作数按位操作,可以用作筛选、合并。 常用的按位运算符有取反(~),与(&),或(|),异或(^)。 除取反外,其余均为两目运算符,如果2个操作数位宽不相等,则用 0 向左扩展补充较短的操作数。
条件运算符¶
形式:cond_expr ? expr1 : expr2
如果cond_expr 为真(即值为1 ),选择expr1 ;如果cond_expr 为假(值为0 ),选择expr2 。如果cond_expr 为 x或z ,结果将是按以下逻辑expr1 和expr2 按位操作的值: 0 与0 得0 , 1 与1 得1 ,其余情况为x 。
移位运算符¶
移位运算符包括左移(<<),右移(>>),算术左移(<<<),算术右移(>>>), 拼接运算符用大括号 {,} 来表示,用于将多个操作数(向量)拼接成新的操作数(向量),信号间用逗号隔开。 算术左移和逻辑左移时,右边低位会补 0。
逻辑右移时,左边高位会补 0;而算术右移时,左边高位会补充符号位,以保证数据缩小后值的正确性。
连接运算符¶
由于非定长常数的长度未知,不允许连接非定常常数。例如{Dbus,5}
此式非法
移位运算符和连接运算符可以帮助我们灵活拼接得到我们需要的不同位的值
宏定义¶
赋值¶
连续赋值¶
连续赋值语句是 Verilog 数据流建模的基本语句,用于对 wire 型变量进行赋值。
在 Verilog 中,我们用 assign 关键字来表示连续赋值,这里大家需要区分一件事,连续赋值只能赋值给 wire 型变量,但是 assign 关键字可以赋值给 reg 型变量,这是过程连续赋值。即 assign 不完全等同于连续赋值。
LHS_target 只能是 wire 型变量,可以是标量或者向量。 RHS_expression 则没有要求, 只要 RHS_expression 表达式的操作数有事件发生(值的变化)时,RHS_expression 就会立刻重新计算,同时赋值给 LHS_target。
因为我们写完代码后,线路已经被焊死,我们无法通过电路本身的电平控制或者设计来重新连接电路,因此大家在实际实验中需要注意 wire 变量仅能连续赋值一次。
过程赋值¶
与连续赋值相对应的就是过程赋值,过程性赋值是在 initial 或 always 语句块里的赋值,赋值对象是寄存器、整数、实数等类型。 这些变量在被赋值后,其值将保持不变,直到重新被赋予新值。
与连续性赋值不同,过程性赋值存在保持的特性,这其实也是电路中寄存器的特性。 与连续性赋值不同,过程赋值分为阻塞赋值和非阻塞赋值。
- 阻塞赋值属于顺序执行,即下一条语句执行前,当前语句一定会执行完毕。阻塞赋值语句使用等号 = 作为赋值符。
- 非阻塞赋值属于并行执行语句,即下一条语句的执行和当前语句的执行是同时进行的,它不会阻塞位于同一个语句块中后面语句的执行。 非阻塞赋值语句使用小于等于号 <= 作为赋值符。
我们在实际的操作中, 不要混用阻塞赋值和非阻塞赋值 ,因为这样会导致时序不易控制,造成意向不到的后果。
在设计电路时,always 时序逻辑块中多用非阻塞赋值,always 组合逻辑块中多用阻塞赋值;在仿真电路时,initial 块中一般多用阻塞赋值。
例如:
此时a<=b 与 b<=a 可以相互不干扰的执行,达到交换寄存器值的目的。
结构建模¶
模块定义结构¶
实例化语句¶
- 在实例化中,有些管脚可能没用到,可在映射中采用空白处理,如:
对输入管脚悬空的,则该管脚输入为高阻 Z,输出管脚被悬空的,该输出管脚废弃不用
- 当端口长度和局部端口表达式长度不同时,端口通过无符号数的右对齐或截断方式进行匹配。
在该例中,Bdl[2]连接到Pba[0], Bdl[1] 连接到Pba[1],余下的输入端口Pba[5]、 Pba[4]和Pba[3]悬空,因此为高阻态z 。与之相似, Mpr[6]连接到Ppy[0], Mpr[5]连接到Ppy[1], Mpr[4] 连接到Ppy[2],其余截断。
过程结构¶
initial语句¶
initial 语句从 0 时刻开始执行,只执行一次,多个 initial 块之间是相互独立的。
如果 initial 块内包含多个语句,需要使用关键字 begin 和 end 组成一个块语句。
initial语句大多用于仿真时模拟输入电平,或者用于初始化器件的寄存器。
always语句¶
与 initial 语句相反,always 语句是重复执行的。always 语句块从 0 时刻开始执行其中的行为语句;当执行完最后一条语句后,便再次执行语句块中的第一条语句,如此循环反复。
always 语句搭配事件控制符号@可以达到特定条件下执行特定事件的作用。
代码的语句¶
条件语句、多路分支语句和循环语句,都需要在过程结构中,即都需要在 initial 语句或者 always 语句中。
条件语句¶
在Verilog HDL中,else 与最近的没有else 的if 相关联
对if 语句,除非在时序逻辑中, if 语句需要有else 语句。若没有缺省语句,设计将产生一个锁存器,锁存器在ASIC设计中有诸多的弊端(可看同步设计技术所介绍)。如下一例:
没有else 语句,当T为1(真)时, D 被赋值给Q,当T为0(假)时,因为没有else 语句,电路保持 Q 以前的值,这就形成一个锁存器。
如果我们希望条件语句写在过程结构外面,可以考虑使用线网连续赋值: