1 什么是Verilog HDL
Verilog HDL(Hardware Description Language)是在C语言的基础上发展起来的一种硬件描述语言,可以用文本形式来描述数字系统硬件的结构和行为,像是逻辑电路图、逻辑表达式等。
1.1发展历程
Verilog 语言最初是于 1983 年由 Gateway Design Automation 公司为其模拟器产品开发的硬件建模语言。由于他们的模拟仿真器产品的广泛使用,Verilog HDL 作为一种便于使用且实用的语言逐渐为众多设计者所接受。
Verilog 语言于 1995 年成为 IEEE 标准,称为Verilog-95。设计人员在使用 Verilog-95 的过程中发现了一些可改进之处。为了解决用户在使用此版本 Verilog 过程中反映的问题,Verilog 进行了修正和扩展,这个扩展后的版本也成为了IEEE 标准,称为Verilog-2001。Verilog-2001 是对 Verilog-95 的一个重大改进版本,它具备一些新的实用功能,例如敏感列表、多维数组、生成语句块、命名端口连接等。
目前,Verilog-2001 是 Verilog 的最主流版本,被大多数商业电子设计自动化软件支持。
1.2为啥需要它
在 FPGA 设计里面,我们有多种设计方式,如原理图设计方式、编写描述语言(代码)等方式。
一开始很多工程师对原理图设计方式很钟爱,这种输入方式能够很直观的看到电路结构并快速理解,但是随着电路设计规模的不断增加,逻辑电路设计也越来越复杂,这种设计方式已经越来越不满足实际的项目需求了。
想像一下大规模集成电路,他们要咋画?难度太大了。要是画错了就更加麻烦了
这个时候 Verilog 语言就取而代之了,目前 Verilog 已经在 FPGA 发/IC 设计领域占据绝对的领导地位。
1.3它的作用
数字电路设计者利用这种语言,可以从顶层到底层逐层描述自己的设计思想,用一系列分层次的模块来表示极其复杂的数字系统。然后利用电子设计自动化(EDA)工具,逐层进行仿真验证,再把其中需要变为实际电路的模块组合,经过自动综合工具转换到门级电路网表。接下来再用专用集成电路 ASIC 或FPGA 自动布局布线工具,把网表转换为要实现的具体电路结构。
1.4Verilog 与 VHDL
这两种语言都是用于数字电路系统设计的硬件描述语言,而且都已经是 IEEE 的标准。
VHDL | Verilog | |
---|---|---|
成为IEEE标准的时间 | 1987 | 1995 |
来源 | 美国军方组织开发的 | 由一个公司的私有财产转化而来 |
难易程度 | 相对要难一点 这个是因为 VHDL 不是很直观,一般认为至少要半年以上的专业培训才能掌握。 |
容易掌握 只要有 C 语言的编程基础,通过比较短的时间,经过一些实际的操作,可以在 1 个月左右掌握。 |
1.5Verilog 和 C
Verilog 是硬件描述语言,在编译下载到 FPGA 之后,FPGA 会生成电路,所以 Verilog 全部是并行处理与运行的;
C 语言是软件语言,编译下载到单片机/CPU 之后,还是软件指令,而不会根据你的代码生成相应的硬件电路,而单片机/CPU 处理软件指令需要取址、译码、执行,是串行执行的。
Verilog 和 C 的区别也是 FPGA 和单片机/CPU 的区别,由于 FPGA 全部并行处理,所以处理速度非常快,这个是 FPGA 的最大优势,这一点是单片机/CPU 替代不了的。
2 语法入门:
2.1逻辑值
逻辑电路中有四种值,即四种状态:
- 逻辑 0:表示低电平,也就是对应我们电路的 GND;
- 逻辑 1:表示高电平,也就是对应我们电路的 VCC;
- 逻辑 X:表示未知,有可能是高电平,也有可能是低电平;
- 逻辑 Z:表示高阻态,外部没有激励信号是一个悬空状态。
高阻,或者说“阻高”,是一种电路分析时的特殊情况。简单来说,它可以被看作是具有极高的输入(输出)电阻,在极限状态下就成为了断路。
严谨地说,高阻态(High_impedance state)、高输出态(High output state)和低输出态(Low output state)共同组成了三态逻辑。
这里要说明一下:真实电路中电平信号是连续变化的,我们定义 VCC 代表电路电压(也就是电压上限值),而数字电路则对 0~VCC 的范围进行了区间划分。例如:0 ~ 0.3VCC 为低电平(0),0.7VCC ~ VCC 为高电平(1),0.3VCC ~ 0.7VCC 之间的则不属于工作电压,而高阻态往往被认为是理想的 0 电压。 因此,低输出态不等于没有输出;高阻则在行为上更接近于没有输出。
因此,如果两个输出端口被导线直接相连,且二者一个处于高电平状态,另一个处于低电平状态,此时相连的导线上电平就会出现混乱,即 1+0=?。但处于高阻态的端口不会影响另一个端口的输出结果,即 z+0=0,z+1=1。这一特性使得三态门被广泛应用与总线(Bus)结构之中。
2.2标识符与命名规范
标识符(identifier)用于定义模块名、端口名和信号名等。
命名规则:只能是下列4种的组合。标识符的第一个字符必须是字母或者下划线。
- 字母
- 数字
- $
- _(下划线)
标识符大小写敏感,是区分大小写的,毕竟它们的ASCII码不同。
标识符的几个例子:
Count和COUNT //两者不同
R56_68
WIRE //区别于关键字wire
FIVE$
4bit //错误命名
不建议大小写混合使用,普通内部信号建议全部小写,参数定义建议大写。
规范建议:
1、用有意义的有效的名字如 sum、cpu_addr 等;(见名知意)
2、用下划线区分词语组合,如 cpu_addr;(而不是用大小写混合的方式CpuAddr;
3、采用一些前缀或后缀,比如:时钟采用 clk 前缀:clk_50m,clk_cpu;低电平采用_n 后缀:
enable_n;(这几乎已经成为了行业标准)
4、统一缩写,如全局复位信号 rst。
5、同一个信号在不同层次需保持一致性,如同一时钟信号必须在各模块保持一致。
6、自定义的标识符不能与保留字(关键词)同名。(例如module、begin、wire等)
7、参数统一采用大写,如定义参数使用 SIZE。
2.3数字与进制
Verilog 数字进制格式包括二进制、八进制、十进制和十六进制。
- b/B二进制:4’b0101 表示 4 位二进制数字 0101,值为5;
- o/O八进制:'o617 表示 32 位八进制数字 618,值为399;
- d/D十进制:5201314 表示 32 位宽十进制数字 5201314
- h/H十六进制:4'Ha表示 4位宽十六进制数字 a,值为10。
十六进制中的 a∼f 字符是不区分大小写的,例如 4'ha=4'hA
。
声明时可以不用指明位宽,位宽和编译器有关,一般为32 bit。
当代码中没有指定数字的位宽与进制时,默认为 32 位的十进制。比如直接写数字100,实际上表示的值为32’D100,即32位宽的10进制数100。
数字表达方式有以下三种:
1) <位宽><进制><数字>这是一种全面的描述方式。
2) <进制><数字>在这种描述方式中,数字的位宽采用缺省位宽(32位)
3) <数字>在这种描述方式中,数字的进制采用缺省进制(十进制)。
一个4位二进制数的数字其位宽为4,一个4位十六进制数的数字其位宽为16(因为每单个十六进制数就要用4位二进制数来表示)。见下例:
8'b1100_1100 //位宽为8的二进制数字,值为204
8'ha2 //位宽为8的2位十六进制数字。
注意:整数表示中,如果填写的二进制位宽小于实际位宽,则会根据填写的位宽对实际数据进行截断。在编译器中,这种操作极有可能会触发 Warning。例如:
3'hfff
填写的二进制位宽为 3bit,最终会被截断为3'b111
。
文章推荐
FPGA 21 ,深入理解 Verilog 中的基数,以及二进制数与十进制数之间的关系( Verilog中的基数 )-CSDN博客
2.4数据类型
主要有三大类数据类型,即寄存器类型、线网类型和参数类型。
从名称中我们也可以看出,真正在数字电路中起作用的数据类型应该是:寄存器类型和线网类型,其余类型可以理解为这两种数据类型的扩展或辅助。
如果省略 width 不写,则默认变量的位宽为 1(等价于
[0:0]
)。无论是 wire 型变量还是 reg 型变量,Verilog 统一将位宽为 1 的变量称作标量(Scalar),位宽大于 1 的变量称作向量(Vector)。在表达式中,我们可任意选择向量中的一位或相邻几位,分别称为位选择(bit-select)和域选择(part-select)。如果选中的位宽超出了实际范围,部分编译器会报错,部分编译器则会自动将信号接地(接 0)。例如:
wire [4:0] bit_test = 5'b01011 bit_test[0] //位选择:表示最低位,值为 1'b1 bit_test[3:2] //域选择:表示第4位和第3位,值为 2'b10 bit_test[4] //表示第5位,值为 1'b0
1、线网类型: wire
线网类型表示元件之间的物理连线/导线。
线网类型不能保存状态,必须由其他逻辑驱动。它的值 由 驱动它的原件的输出值 决定。
如果没有驱动原件连接到线网,即线网缺省时,它的值为Z高阻态。因为啥也没接到线上,悬空!
wire 是 Verilog 的默认数据类型。(也就是说,对于没有显式声明类型的信号,Verilog 一律将其默认为 wire 类型)
任何已经声明为wire 型变量的连续赋值语句都是以assign开头。
//wire define
wire data_en; //没指定位宽默认是1bit。数据使能信号
wire a,b,c; //连续定义3个位宽为1的信号
wire [7:0] data; //1个字节长度/位宽的数据。当位宽大于 1 时,声明为向量的形式
wire gnd = 1'b0; //定义时也可以赋值,但是不推荐这样的代码风格
wire [8:2] addr ; //声明7bit位宽的线型变量addr,位宽有效范围为8:2
assign a = b + c;
2、 寄存器类型:reg、integer
寄存器类型表示数据存储单元。
它只能在always和initial中被赋值,被赋值后它会保持数据原有的值,直到再次被改写。
寄存器类型的缺省值是x。就像现实的电路那样,上电前不知道是0还是1。
//reg define
reg clk_temp; //缺省位宽说明默认为1个bit长度
reg flag1, flag2; //声明两个1位宽的寄存器型变量
reg [0:31] data; //声明32bit位宽的寄存器变量data, 最高有效位为0,最低位为31
reg [7:0] counter; //声明1字节长度的reg型
- 如果赋值过程的语句描述的是时序逻辑,即always语句带有时钟信号,则该寄存器变量对应为寄存器/边沿触发器。时钟信号为变量的状态更新提供了一个明确的时间参考点,使得变量的值在每个时钟周期都会根据输入条件进行更新,所以即使有输入信号也不会立马改变输出信号。
- 如果该过程语句描述的是组合逻辑,即always 语句不带有时钟信号,则该寄存器变量对应为硬件连线wire型变量。因为没有时钟,所以会立马做出数据的更改,这种赋值是基于输入信号的即时变化,它更接近于组合逻辑的行为。硬件综合工具通常会将这些变量实现为硬件连线(
wire
)类型的逻辑网络。
reg 作为 Verilog 的类型关键字实际上是具有误导性的。与 wire 类型代表线网不同,一个 reg 类型的变量不一定对应一个寄存器(Register)。
reg 关键字只是声明了一个在 always 语句中进行赋值的信号。如果 always 描述的是组合逻辑,那么 reg 就会综合成一根线,如果 always 描述的是时序逻辑,那么 reg 才会综合成一个寄存器。
可以这样想:在Verilog中,
reg
类型并不直接对应于硬件中的寄存器;它更多地是一个表示该变量将在always/initial
块中被赋值的声明。当
always
块描述组合逻辑时,尽管变量被声明为reg
类型,但硬件实现上可能并不包含钟控寄存器。相反,它可能是一个由逻辑门组成的组合逻辑网络。
//时序逻辑电路中的reg代码例子:
always @(posedge clk) begin
reg_var <= some_expression;
end
//在这个例子中,reg_var是一个reg类型的变量,它在每个时钟上升沿都会根据some_expression的值进行更新。在硬件实现中,这通常对应于一个寄存器或边沿触发器。
//组合逻辑电路中的reg代码例子
always @(*) begin //是一种组合逻辑块,表示只要输入信号有变化都会执行语句块
//()括号里被称为“敏感列表”,*星号表示它对所有输入信号都敏感。
result = a + b; //输入信号a,b发生变化时, 结果result也会随之立马变化
end
//例子说明了组合逻辑的特点:它直接反映了当前所有相关输出端口上的最新数据,不存在延迟机制去等待时钟的到来。
2、 参数类型:parameter
参数其实就是一个常量。
作用:常被用于定义状态机的状态、数据位宽和延迟大小等,由于它可以在编译时修改参数的值,因此它又常被用于一些参数可调的模块中,使用户在实例化模块时,可以根据需要配置参数。
由于是常量,所以变量只能被赋值一次,赋值后的参数值不再改变。
需要注意的是参数的定义是局部的,只在当前模块中有效。
声明位置有以下2种:
1 模块头部声明(推荐方式):
module module_name #(
parameter WIDTH = 8,
parameter DEPTH = 16
)
(
input [WIDTH - 1 : 0] data_in,
input [DEPTH - 1 : 0] data_out
);
2 模块内部声明:
module MyModule (
input clk,
...
);
parameter DATA_WIDTH = 8; //32'D8
parameter i = 1, j = 2, k = 3; //32'D1、32'D2、32'D3
//还记得标识符里讲的命名规则吗?参数统一采用大写字母表示,用下划线区分词语。
//哪小写字母用于什么信号的命名呢?
2.5运算符
Verilog 中的运算符按照功能可以分为下述7种类型:
1、算术运算符
2、关系运算符
3、逻辑运算符
4、条件运算符
5、位运算符
6、移位运算符
7、拼接运算符 复制
8、归约运算符