Verilog 语句块提供了将两条或更多条语句组成语法结构上相当于一条一句的机制。主要包括两种类型:顺序块和并行块
。
顺序块
并行块
条件语句
条件语句基本结构如下:
if (condition1) true_statement1 ;
else if (condition2) true_statement2 ;
else default_statement ;
多路分支语句
case语句基本结构如下:
case(case_expr)
condition1 : true_statement1 ;
condition2 : true_statement2 ;
……
default : default_statement ;
endcase
循环语句
while循环
while循环基本结构如下:
while (condition) begin
…
end
while 循环中止条件为 condition 为假,如果开始执行到 while 循环时 condition 已经为假,那么循环语句一次也不会执行。
for循环
for循环基本结构如下:
for(initial_assignment; condition ; step_assignment) begin
…
end
initial_assignment 为初始条件。condition 为终止条件,condition 为假时,立即跳出循环。step_assignment 为改变控制变量的过程赋值语句,通常为增加或减少循环变量计数。因为初始条件和自加操作等过程都已经包含在 for 循环中,所以 for 循环写法比 while 更为紧凑。
repeat循环
repeat循环基本结构如下:
repeat (loop_times) begin
…
end
repeat 的功能是执行固定次数的循环,它不能像 while 循环那样用一个逻辑表达式来确定循环是否继续执行。repeat 循环的次数必须是一个常量、变量或信号。如果循环次数是变量信号,则循环次数是开始执行 repeat 循环时变量信号的值。即便执行期间,循环次数代表的变量信号值发生了变化,repeat 执行次数也不会改变。
forever循环
forever循环基本结构如下:
forever begin
…
end
forever 语句表示永久循环,不包含任何条件表达式,一旦执行便无限的执行下去,系统函数 $finish 可退出 forever。forever 相当于 while(1) 。通常,forever 循环是和时序控制结构配合使用的。
过程连续赋值是过程赋值
的一种。这种赋值语句能够替换其他所有 wire 或 reg 的赋值,改写了 wire 或 reg 型变量的当前值。
与过程赋值不同的是,过程连续赋值的表达式能被连续的驱动到 wire 或 reg 型变量中,即过程连续赋值发生作时,右端表达式中任意操作数的变化都会引起过程连续赋值语句的重新执行。
过程连续性赋值主要有 2 种,assign-deassign 和 force-release
。
assign, deassign
force, release
结构建模方式有 3 类描述语句: Gate(门级)例化语句,UDP(用户定义原语)例化语句和 module(模块)例化语句。本次主要讲述使用最多的模块级例化语句。
模块
模块是 Verilog 中基本单元的定义形式,是与外界交互的接口。模块定义必须以关键字 module 开始,以关键字endmodule 结束。模块格式定义如下:
module module_name
#(parameter_list)
(port_list) ;
Declarations_and_Statements ;
endmodule
端口
端口是模块与外界交互的接口。对于外部环境来说,模块内部是不可见的,对模块的调用只能通过端口连接进行。
端口列表
模块的定义中包含一个可选的端口列表,一般将不带类型、不带位宽的信号变量罗列在模块声明里。
端口声明
模块例化
在一个模块中引用另一个模块,对其端口进行相关连接,叫做模块例化。
命名端口连接
将需要例化的模块端口与外部信号按照其名字进行连接,端口顺序随意,可以与引用 module 的声明端口顺序不一致,只要保证端口名字与外部信号匹配即可。如果某些输出端口并不需要在外部连接,例化时 可以悬空不连接,甚至删除。一般来说,input 端口在例化时不能删除,否则编译报错,output 端口在例化时可以删除。
顺序端口连接
这种方法将需要例化的模块端口按照模块声明时端口的顺序与外部信号进行匹配连接,位置要严格保持一致。
端口连接规则
带参数例化
当一个模块被另一个模块引用例化时,高层模块可以对低层模块的参数值进行改写。这样就允许在编译时将不同的参数传递给多个相同名字的模块,而不用单独为只有参数不同的多个模块再新建文件。参数覆盖有 2 种方式:1)使用关键字 defparam,2)带参数值模块例化。
defparam 语句
可以用关键字 defparam 通过模块层次调用的方法,来改写低层次模块的参数值。
带参数模块例化
第二种方法就是例化模块时,将新的参数值写入模块例化语句,以此来改写原有 module 的参数值。
函数
函数只能在模块中定义,位置任意,并在模块的任何地方引用,作用范围也局限于此模块。函数主要有以下几个特点:
Verilog 函数声明格式如下:
function [range-1:0] function_id ;
input_declaration ;
other_declaration ;
procedural_statement ;
endfunction
函数调用格式如下:
function_id(input1, input2, …);
常数函数
常数函数是指在仿真开始之前,在编译期间就计算出结果为常数的函数。常数函数不允许访问全局变量或者调用系统函数,但是可以调用另一个常数函数。这种函数能够用来引用复杂的值,因此可用来代替常量。
automatic 函数
在 Verilog 中,一般函数的局部变量是静态的,即函数的每次调用,函数的局部变量都会使用同一个存储空间。若某个函数在两个不同的地方同时并发的调用,那么两个函数调用行为同时对同一块地址进行操作,会导致不确定的函数结果。Verilog 用关键字 automatic 来对函数进行说明,此类函数在调用时是可以自动分配新的内存空间的,也可以理解为是可递归的。因此,automatic 函数中声明的局部变量不能通过层次命名进行访问,但是 automatic 函数本身可以通过层次名进行调用。
任务与函数的区别
和函数一样,任务(task)可以用来描述共同的代码段,并在模块内任意位置被调用,让代码更加的直观易读。函数一般用于组合逻辑的各种转换和计算,而任务更像一个过程,不仅能完成函数的功能,还可以包含时序控制逻辑。下面对任务与函数的区别进行概括:
比较点 | 函数 | 任务 |
---|---|---|
输入 | 函数至少有一个输入,端口声明不能包含 inout 型 | 任务可以没有或者有多个输入,且端口声明可以为 inout 型 |
输出 | 函数没有输出 | 任务可以没有或者有多个输出 |
返回 | 函数至少有一个返回值 | 任务没有返回值 |
仿真 | 函数总在零时刻就开始执行 | 任务可以在非零时刻执行 |
时序 | 函数不能包含任何时序控制逻辑 | 任务不能出现 always 语句,但可以包含其他时序控制,如延时语句 |
调用 | 函数只能调用函数,不能调用任务 | 任务可以调用函数和任务 |
书写 | 函数不能单独作为一条语句出现,只能放在赋值语言的右端 | 任务可以作为一条单独的语句出现语句块中 |
任务
任务声明:任务在模块中任意位置定义,并在模块内任意位置引用,作用范围也局限于此模块。模块内子程序出现下面任意一个条件时,则必须使用任务而不能使用函数。
Verilog 任务声明格式如下:
task task_id ;
port_declaration ;
procedural_statement ;
endtask
任务中使用关键字 input、output 和 inout 对端口进行声明。input 、inout 型端口将变量从任务外部传递到内部,output、inout 型端口将任务执行完毕时的结果传回到外部。
进行任务的逻辑设计时,可以把 input 声明的端口变量看做 wire 型,把 output 声明的端口变量看做 reg 型。但是不需要用 reg 对 output 端口再次说明。
对 output 信号赋值时也不要用关键字 assign。为避免时序错乱,建议 output 信号采用阻塞赋值。
Verilog 是硬件描述语言,硬件电路是并行执行的,当需要按照流程或者步骤来完成某个功能时,代码中通常会使用很多个 if 嵌套语句来实现,这样就增加了代码的复杂度,以及降低了代码的可读性,这个时候就可以使用状态机来编写代码。状态机相当于一个控制器
,它将一项功能的完成分解为若干步,每一步对应于二进制的一个状态,通过预先设计的顺序在各状态之间进行转换,状态转换的过程就是实现逻辑功能的过程。
状态机
,全称是有限状态机(Finite State Machine,缩写为 FSM),是一种在有限个状态之间按一定规律转换的时序电路,可以认为是组合逻辑和时序逻辑的一种组合。状态机通过控制各个状态的跳转来控制流程,使得整个代码看上去更加清晰易懂,在控制复杂流程的时候,状态机优势明显,因此基本上都会用到状态机。
根据状态机的输出是否与输入条件相关, 可将状态机分为两大类, 摩尔(Moore)型状态机和米勒(Mealy)型状态机
。
米勒状态机的模型如下图所示,模型中第一个方框是指产生下一状态的组合逻辑 F,F 是当前状态和输入信号的函数,状态是否改变、如何改变,取决于组合逻辑 F 的输出;第二框图是指状态寄存器,其由一组触发器组成,用来记忆状态机当前所处的状态,状态的改变只发生在时钟的跳边沿;第三个框图是指产生输出的组合逻辑 G,状态机的输出是由输出组合逻辑 G 提供的,G 也是当前状态和输入信号的函数。
摩尔状态机的模型如下图所示,对比米勒状态机的模型可以发现,其区别在于米勒状态机的输出由当前状态和输入条件决定的,而摩尔状态机的输出只取决于当前状态。
产生原因
数字电路中,信号传输与状态变换时都会有一定的延时。在组合逻辑电路中,不同路径的输入信号变化传输到同一点门级电路时,在时间上有先有后,这种先后所形成的时间差称为竞争(Competition)。由于竞争的存在,输出信号需要经过一段时间才能达到期望状态,过渡时间内可能产生瞬间的错误输出,例如尖峰脉冲。这种现象被称为冒险(Hazard)。
竞争不一定有冒险,但冒险一定会有竞争。
其实实际硬件电路中,只要门电路各个输入端延时不同,就有可能产生竞争与冒险。
判断方法
Y = A + A'
或
Y = A · A'
则可判定此逻辑存在竞争与冒险。
消除方法
对数字电路来说,常见的避免竞争与冒险的方法主要有 3 种。
1)增加滤波电容,滤除窄脉冲
。此种方法需要在输出端并联一个小电容,将尖峰脉冲的幅度削弱至门电路阈值以下。此方法虽然简单,但是会增加输出电压的翻转时间,易破坏波形。
2)修改逻辑,增加冗余项
。利用卡诺图,在两个相切的圆之间,增加一个卡诺圈,并加在逻辑表达式之中。
3)使用时钟同步电路,利用触发器进行打拍延迟
。同步电路信号的变化都发生在时钟边沿。对于触发器的 D 输入端,只要毛刺不出现在时钟的上升沿并且不满足数据的建立和保持时间,就不会对系统造成危害,因此可认为 D 触发器的 D 输入端对毛刺不敏感。 利用此特性,在时钟边沿驱动下,对一个组合逻辑信号进行延迟打拍,可消除竞争冒险。
门级建模,是使用基本的逻辑单元,例如与门,与非门等,进行更低级抽象层次上的设计。与行为级建模相比,门级建模更注重硬件的实现方法,即通过连接一些基本门电路去实现多种逻辑功能。虽然行为级建模最后也会被综合成基本的门级电路网络,但对于复杂的设计来说,行为级建模的效率远远高于门级建模。所以目前 Verilog 大多数用于描述数字设计的行为级层次(RTL),一般只注重设计实现的算法或流程,而不用特别关心具体的硬件实现方式。
多输入门
多输入门只有单个输出,有单个或多个输入端。Verilog 内置多输入门如下:and(与门) nand(与非门) or(或门) nor(或非门) xor(异或门) nxor(同或门)
。使用基本的逻辑门单元去实现一些简单的逻辑功能时,使用模块例化的方式即可。门级单元第一个端口是输出,后面端口是输入,例化调用时需要注意。门级单元实例调用的时候,也可以不指定实例的名字。
当输入端口超过 2 个时,只需将输入信号在端口列表中继续排列即可,Verilog 可自动识别。多输入门的真值表如下,注意输出不会出现 Z。
and(与门)的真值表如下:
and | 0 | 1 | x | z |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
1 | 0 | 1 | x | x |
x | 0 | x | x | x |
z | 0 | x | x | x |
nand(与非门)的真值表如下:
nand | 0 | 1 | x | z |
---|---|---|---|---|
0 | 1 | 1 | 1 | 1 |
1 | 1 | 0 | x | x |
x | 1 | x | x | x |
z | 1 | x | x | x |
or(或门)的真值表如下:
or | 0 | 1 | x | z |
---|---|---|---|---|
0 | 0 | 1 | x | x |
1 | 1 | 1 | 1 | 1 |
x | x | 1 | x | x |
z | x | 1 | x | x |
nor(或非门)的真值表如下:
nor | 0 | 1 | x | z |
---|---|---|---|---|
0 | 1 | 0 | x | x |
1 | 0 | 0 | 0 | 0 |
x | x | 0 | x | x |
z | x | 0 | x | x |
xor(异或门)的真值表如下:
xor | 0 | 1 | x | z |
---|---|---|---|---|
0 | 0 | 1 | x | x |
1 | 1 | 0 | x | x |
x | x | x | x | x |
z | x | x | x | x |
nxor(同或门)的真值表如下:
nxor | 0 | 1 | x | z |
---|---|---|---|---|
0 | 1 | 0 | x | x |
1 | 0 | 1 | x | x |
x | x | x | x | x |
z | x | x | x | x |
多输出门
多输出门只有单个输入,有单个或多个输出端,又可称之为 buffer,起缓冲、延时作用。内置多输入门有buf(缓冲器)和not(非门)
。多输出门的真值表如下,注意输出不会出现 Z。
buf | 0 | 1 | x | z |
---|---|---|---|---|
输出 | 0 | 1 | x | x |
not | 0 | 1 | x | z |
---|---|---|---|---|
输出 | 1 | 0 | x | x |
三态门
Verilog 中还提供了 4 个带有控制端的 buffer 门单元,称为三态门。只有当控制信号有效时,数据才能正常传递,否则输出为高阻抗状态 Z。
三态门的真值表如下,表中有些为可选项,例如,1/z 表明,根据输入端和控制端的信号强度,输出端既可能为 1,也可能为 z。
bufif1的真值表如下:
bufif1 | 控制端 | |||
---|---|---|---|---|
0 | 1 | x | z | |
0 | z | 0 | 0/z | 0/z |
1 | z | 1 | 1/z | 1/z |
x | z | x | x | x |
z | z | x | x | x |
bufif0的真值表如下:
bufif0 | 控制端 | |||
---|---|---|---|---|
0 | 1 | x | z | |
0 | 0 | z | 0/z | 0/z |
1 | 1 | z | 1/z | 1/z |
x | x | z | x | x |
z | x | z | x | x |
notif1的真值表如下:
notif1 | 控制端 | |||
---|---|---|---|---|
0 | 1 | x | z | |
0 | z | 1 | 1/z | 1/z |
1 | z | 0 | 0/z | 0/z |
x | z | x | x | x |
z | z | x | x | x |
notif0的真值表如下:
notif0 | 控制端 | |||
---|---|---|---|---|
0 | 1 | x | z | |
0 | 1 | z | 1/z | 1/z |
1 | 0 | z | 0/z | 0/z |
x | x | z | x | x |
z | x | z | x | x |
MOS 开关
MOS 开关有nmos(N 类型 MOS 管)、pmos(P 类型 MOS 管)、rnmos (带有高阻抗的 NMOS 管)、 rpmos(带有高阻抗的 PMOS 管)
。MOS 管用来为开关逻辑建模,数据从输入流入输出,可通过适当设置来开、关数据流。带有阻抗的 MOS 管,源极到漏极的阻抗较高,且在传递信号时会减小信号的强度。
MOS 管开关结构图如下所示。
MOS 管真值表如下所示,与三态门非常相似。
nmos的真值表如下:
nmos | 控制端 | |||
---|---|---|---|---|
0 | 1 | x | z | |
0 | z | 0 | 0/z | 0/z |
1 | z | 1 | 1/z | 1/z |
x | z | x | x | x |
z | z | x | x | x |
pmos的真值表如下:
pmos | 控制端 | |||
---|---|---|---|---|
0 | 1 | x | z | |
0 | 0 | z | 0/z | 0/z |
1 | 1 | z | 1/z | 1/z |
x | x | z | x | x |
z | x | z | x | x |
CMOS 开关
CMOS 开关用关键字 cmos 和 rcmos (带有高阻抗)声明。CMOS 有一个数据输出,一个数据输入和 2 个控制输入,结构示意图如下:
信号 PControl 与 Ncontrol 通常是互补的。当信号 Ncontrol 为 1 且 PControl 为 0 时,开关导通。 当信号 Ncontrol 为 0 且 PControl 为 1 时,开关输出为高阻。可以将 CMOS 开关看做是 NMOS 与 PMOS 开关的组合体。
例化时,CMOS 管第一个端口为输出端,第二个端口为数据输入端,第三个端口为 Ncontrol 控制输入端,第四个端口为 Pcontrol 控制输入端。
双向开关
NMOS、PMOS、CMOS 开关门都是从漏极向源极导通,方向是单向的。Verilog 中还提供了双向导通的开关器件,数据可以双向流动,两边的信号都可以是驱动信号。双向开关结构图如下:
tran 开关为两个信号直接的缓存,inout1 或 inout2 均可以是驱动信号。tranif1 仅当 control 信号为 1 时,开关两边的信号导通。当 control 为 0 时,两个信号断开,有驱动源的信号会和驱动源保持一致的信号值,没有驱动源的信号则呈现为高阻状态。tranif0 同理。因此,双向开关常用来进行总线或信号之间的隔离。
电源和地
晶体管级电路需要源极(Vdd, 逻辑 1)与地极(Vss, 逻辑 0),分别用关键字 supply1 和 supply0 来定义。 用法如下:
supply1 VDD ;
supply0 GND ;
wire siga = VDD ; //siga is connected to logic 1
wire sigb = GND ; //sign is connected to logic 0
对于数字系统而言,建立时间(setup time)和保持时间(hold time)
是数字电路时序的基础。数字电路系统的稳定性,基本取决于时序是否满足建立时间和保持时间。
基本概念
建立时间
就是时钟触发事件来临之前,数据需要保持稳定的最小时间,以便数据能够被时钟正确的采样。
保持时间
就是时钟触发事件来临之后,数据需要保持稳定的最小时间,以便数据能够被电路准确的传输。
可以通俗的理解为:时钟到来之前,数据需要提前准备好;时钟到来之后,数据还要稳定一段时间。建立时间和保持时间组成了数据稳定的窗口,如下图所示:
同步时钟
数字设计中,一般认为,频率相同或频率比为整数倍、且相位相同或相位差固定的两个时钟为同步时钟
。或者理解为,时钟同源且频率比为整数倍的两个时钟为同步时钟。其实,时钟同源,就保证了时钟相位差的固定性。具体可以分类如下:
异步时钟
工作在异步时钟下的两个模块进行数据交互时,由于时钟相位关系不可控制,很容易导致建立时间和保持时间 violation。以下 3 种情况下的时钟可以认为是异步的:
不同源:由两个不同的时钟源产生的两个时钟是异步的,这是最常见的异步时钟。即便两个时钟频率相同,但是也不能保证每次上电后两者的相位或相位差是相同的,所以信号间的传输与时钟关系也是不确定的。
同源但频率比不是整数倍:两个时钟间相位差也可能会有多个,例如同源的 7MHz 时钟和 3MHz 时钟,他们之间也会出现多个相位差,时序也难以控制。一般情况下也需要当异步时钟处理。
同源虽频率比为整数倍但不满足时序要求:当信号从快时钟域传递到慢时钟域时,只要慢时钟域能安全采集到从快时钟域传来的信号,就不存在异步问题。但如果信号在快时钟域翻转速率过快,慢时钟域可能不会安全的采集到从快时钟域传来的信号,这也可以认为是异步问题。
一般来说,慢时钟域时序约束较为宽松,快时钟域较为严格。
致谢领航者ZYNQ开发板,开启FPGA学习之路!
“皓月”遇见“教师”是团圆遇上了感恩,祝大家双节快乐!!!
希望本文对大家有帮助,上文若有不妥之处,欢迎指正
分享决定高度,学习拉开差距