ZYNQ学习记录FPGA(二)Verilog语言

一、Verilog简介

1.1 HDL(Hardware Description language)

在解释HDL之前,先来了解一下数字系统设计的流程:逻辑设计 -> 电路实现 -> 系统验证。

逻辑设计又称前端,在这个过程中就需要用到HDL,正文名硬件描述语言,本文所要介绍的Verilog就时HDL中的一种,需要注意的是,一般我们说的编程语言如C,C++,Python等其实是在描述软件,而HDL是在描述硬件,这是HDL与其他编程语言的重要区别。

HDL 是描述数字电路的结构和功能的语言,用HDL描述的电路的结构和功能可以通过综合工具(EDA工具)将其转换成门级电路网表(数字电路最基本的单元就是门,门机电路网表可以组合成几乎所有的高级数字电路),有了门级电路网表,再结合特定的工艺(不同厂商有不同的工艺,如FPGA和ASIC),将工艺中的元件和网表一一对应起来,再通过布局布线形成电路结构,就能完成电路的实现,又称后端。

HDL中除了Verilog语言外还有VHDL,这两个HDL中,Verilog在中国和美国应用更加广泛,而VHDL在欧洲应用更广一点。

1.2 Verilog简介

Verilog语育最初是于1983 年 由Gateway Design Automation 公司为其模拟器产品开发的硬件建模语言。

Verilog 语言于1995 年成为IEEE 标准,称为IEEE Std 1364-1995,也就是通常所说的Verilog-95。Verilog-2001是对Verilog-95的一个重大改进版本。

1.3 Verilog 和 C语言的区别

Verilog 是硬件描述语言,在编译下载到FPGA之后,会生成电路,所以Verilog 是并行运行 的;

C语言是软件编程语言,编译下载到单片机之后,是存储器中的一组指令而单片机处理软件指令需要取指、译码、执行,这个过程是串行执行的。

Verilog和C的区别也是FPGA和 单片机/CPU的区别。FPGA由于全并行处理,处理速度非常快,这个是 FPGA 的最大优势,这一点是 单片机/CPU 替代不了的。

二、Verilog语法

2.1 逻辑值

逻辑0表示低电平,对应电路中的GND;

逻辑1表示高电平,对应电路中的VCC;

逻辑X表示未知电平,可能是高也可能是低;

逻辑Z表示高阻态,外部没有激励,处于悬空状态;

注意逻辑Z和逻辑X的区分,逻辑X是可高可低的而逻辑Z是不能判断电平高低的是悬空的。

2.2 数字进制格式

Verilog包括二进制(b)、八进制(o)、十进制(d)和十六进制(h),verilog中定义常量需要说明位数和进制,如4'b1101,4'o15,4d'13,4h'd都表示同一个数值但是不同进制

2.3 标识符(identifier)

标识符类似于C语言中的变量名,它是用来定义模块名、端口名、信号名等的。

定义标识符时应该注意一下三点:

1)标识符可以是任意一组字母、数字、$符号和(下划线)符号的组合;

2)标识符的第一个字符必须是字母或者下划线;

3)标识符是区分大小写的;

一般定义标识符可以遵循以下规则:

1)大小写不混合使用;2)普通内部信号小写表示;3)信号名应该体现信号的含义且应该简洁清晰。

一些标识符举例:sum、cpu_addr、clk_10、clk_4、clk_cpu等

2.4 数据类型

在 Verilog 语言中,主要有三大类数据类型:寄存器 数据类型、线网 数据类型和参数数据类型。前两者是要映射到实际电路中去的,是真正对电路进行描述的数据类型

2.4.1寄存器数据类型

定义寄存器数据的关键字为 reg ,举例如下:

这里定义了两个寄存器变量,也可以叫reg变量,分别为delay_cnt和key_reg,其中delay_cnt还指定了该寄存器变量的位宽为32位(0-31),值得注意的是每一个寄存器变量定义结束时都要有分号,位宽的定义为从大到小且定义reg变量时是禁止给予初始值的。

reg类型的数据只能在 always 语和initial 语句中被赋值。

如果该过程语句描述的是时序逻辑,即always语句带有时钟信号,则该寄存器变量对应的物理意义为触发器

如果该过程语句描述的是组合逻辑,即always语句不带有时钟信号,则该寄存器变量对应的物理意义为硬件连线

2.4.2 线网数据类型

定义线网数据的关键字为 wire 或 tri,两者用法是一致的,但一般用wire,举例如下:

线网数据类型表示结构实体(例如门)之间的物理连线

线网类型的变量不能储存值,它的值是由驱动它的元件所决定的。那能够驱动线网类型变量的元件有门、连续赋值语句、assign等,如果没有驱动元件连接到线网类型的变量上,则该变量就是高阻的,即其值为z。

需要注意的是在举例中没有指定wire变量的位宽,则默认是1位的位宽,当然位宽的定义和reg变量的定义格式相同。

2.4.3 参数数据类型

定义参数数据的关键字为 parameter,实际就是定义常量,举例如下:

参数型数据常用于定义状态机的状态、数据位宽和延迟大小等采用标识符来代表一个常量可以提高程序的可读性和可维护性在模块调用时,可通过参数传递来改变被调用模块中已定义的参数。

2.5 运算符

Verilog的运算符主要有算术运算符、关系运算符、逻辑运算符、条件运算符、位运算符、移位运算符、拼接运算符。

2.5.1 算术运算符

需要注意的是除法运算只能整除,%为取余的操作。

2.5.2 关系运算符

2.5.3 逻辑运算符

从上到下分别为非、与、或运算

2.5.4 条件操作符

条件操作符(条件运算符)是Verilog特有的运算符:

用法举例如下:

result=(a >= b)? a:b;

表示如果a>=b成立则将a的值赋给result否则将b的值赋给result,即取a和b中的最大值。

2.5.5 位运算符

位运算符就是对数据进行按位进行计算并将每一位之间的计算结果合并后返回,当然位运算符只针对二进制数据有效。

需要注意的是当a和b的位宽不一致时,位运算会自动对位宽较小的数据的前面进行补零,补零到位宽一致再进行运算。对于异或操作,可以借助之前的条件操作符来理解:异或就可以写成result=(a!=b)?1:0

2.5.6 移位运算符

需要注意的是两种移位运算都用0来填补移出的空位。当左移时,位宽增加。当右移时,位宽不变。

举例如下:

4'b1001<<2=6'b100100

4'b1001>>1=4 b0100

2.5.7 位拼接运算符

举例如下:

c = {a[7:0],b[1:0]}

这样计算出来的c就应该是一个位宽为9的数据c[8:0]。

2.5.8 运算符优先级

最后将前面介绍的运算符进行优先级排列:

三、Verilog程序框架

3.1 注释

Verilog有两种注释方式:注释单行内容"//";注释范围内容起点用"/*",结尾用"*/"。

3.2 关键字

除了前面介绍的变量定义所用的关键字外,Verilog中还有许多关键字,定义标识符应该避免与这些关键字重名:

3.3 框架结构

3.3.1 模块结构(module)

模块是Verilog中的基本设计单元,可以对照C语言中的函数来理解模块的含义。

模块一般分为两部分,一部分描述模块的接口,一部分描述模块的逻辑功能。这两部分也同样可以参照C语言中的函数输入变量和函数内容来理解。这两部分可以细分为端口定义,IO说明,内部信号声明,功能定义四个部分,前两部分属于接口的描述,后面两个部分用于描述逻辑功能。

举例如下:

这里描述模块功能时使用了assign关键字,这是给线网变量赋值的一种方法。只是这里没有声明内部信号,只利用了接口的信号就实现了功能。

需要注意的是每一个模块都必须以module关键字开头,以endmodule关键字结尾;input和output定义的变量默认时wire类型的变量;内容中的每一行结束都应该有分号结尾。

3.3.2 功能定义

功能定义的方法有三种:assign关键字、always关键字、例化实例元件。其中assign只能用于描述组合逻辑功能,always可以描述组合和时序逻辑功能。需要注意的是这三种方式所定义的功能总是并行运行的,不论其在代码中的位置如何,这一点和C语言、python等软件描述语言的串行执行代码是完全不同的。

3.3.2.1 always关键字

下面对always关键字进行详细说明:

其语法如下:

复制代码
always @(信号) begin
    // 语句块
end

这里的信号表示只有该信号触发时就执行语句块的内容(串行执行),信号可以是边沿触发,也可以是电平触发。边沿触发需要用到posedge(上升沿)和negedge(下降沿)关键字后面加信号名的方式来表示边沿触发的类型。电平触发只需信号名来表示,信号为高电平时触发always,低电平时就不触发always,举例如下:

如图中代码所示 ,always语句的功能内容由begin关键字标志开始,end关键字标志结束,begin和end之间的内容是串行进行的,即逐行运行。对于电平触发有一种简单的书写方式:

用"*"代表所有的输入变量信号,这里"*"表示"a or b or c or d or e or f or g or h or p or m"
3.3.2.2 模块调用(模块例化)

例化的语法如下:

复制代码
被例化模块本来的名字 #(
    .常量参数名 (要赋给该常量参数的值)
)例化名(
    .被例化模块的接口变量名 (传入的与该接口相对应的变量名),
);

可以把例化模块对比软件描述语言中的函数的调用,不外乎就是调用函数名、传入参数,只不过这里"调用函数名"还要给"函数"一个新的名字,称为例化名,传入参数时要传入模块的接口变量和模块中定义的常量的值,这样例化后就可以实现模块之间信号的传递了。下面举一个实列来帮助理解:

左图是顶层模块对模块的例化,右图是被例化模块的定义。需要注意的是在例化时,被例化模块的输入接口可以连接reg和wire类型的变量,但是输出接口只能连接wire类型的变量,如在例化time_out模块时,在连接该模块的flag信号时就专门定义了一个wire类型的add_flag变量来连接。

四、进阶Verilog知识点

4.1 结构语句

结构语句有initial和always两种语句分别由initial和always关键字来开始,区别在于initial语句在程序中只执行一次,而always语句是重复运行的。initial常用于测试文件的编写,用来产生仿真测试信号激励信号),或者用于对存储器变量赋初值。always则用来描述模块功能。举例如下:

图中的#20这样的#+数字的组合表示延时,延时时间的单位由仿真文件的开头,如`timescale 1ns / 1ps决定,这里是ns为单位,#20就表示延时20ns。

4.2 赋值语句

Verilog中的赋值包含阻塞赋值和非阻塞赋值两种模式,形式如下:

|-----------|-------------|
| 阻塞赋值 | b = a |
| 非阻塞赋值 | b <= a |

我们把a叫做RHS(Right Hand Side),b叫做LHS(Left Hand Side)。那么对于阻塞赋值它实际上做的操作是计算RHS再更新LHS,当在always的语句块中执行时,由于执行过程是串行的,所以必须等阻塞赋值结束后,即更新完LHS后,才会进行下一段代码,这里可以看出阻塞赋值是将计算RHS和更新LHS作为一个整体,一段一段的运行的,颇有阻塞的感觉。对于非阻塞赋值,它把计算RHS和更新LHS两个部分拆开了,并且在计算非阻塞赋值的RHS以及更新LHS期间,允许其他的非阻塞赋值语句同时计算RHS和更新LHS,这样就做到了相较于阻塞赋值的非阻塞赋值。

对于阻塞赋值和非阻塞赋值怎么选?什么时候用?这两个问题可以参照下面的规定:

1)在描述组合逻辑的 always 块中用阻塞赋值"=",综合成组合逻辑的电路结构;

这种电路结构只与输入电平的变化有关系。

2)在描述时序逻辑的 always 块中用非阻塞赋值<,综合成时序逻辑的电路结构;

这种电路结构往往与触发沿有关系,只有在触发沿时才可能发生赋值的变化。

注意:在同一个always块中不要既用非阻塞赋值又用阻塞赋值;不允许在多个always块中对同 一个变量进行赋值!

4.3 条件语句

条件语句有if-else和case两种 ,if语句语法如下:

复制代码
if(表达式1)
    语句1;
else if(表达式2)
    语句2;
else if(表达式3)
    语句3;
else
    语句4;

if(a) begin
    语句1;
    语句2;
end
else begin
    语句3;
    if(!b)
        语句4;
    else
        语句5;
end

需要注意的是,在if进行值的判断时,0(低电平)、x(未知)、z(高阻态)均表示假。用begin,end的组合可以使得一个if或else包含多个语句。

对于case语句,举例如下:

case的内容中包含分支表达式和控制表达式,分支表达式表示需要被满足的条件,控制表达式则表示满足条件后执行的内容。需要注意的是在写分支表达式时,位宽必须和输入num的位宽一致且不能省略。default表示所有分支表达式都不满足时就执行default的内容。

case在Verilog中还有两种写法casez和casex

casez表示比较时不考虑高阻值,casex表示比较时不考虑高阻值和未知值。

五、总结

在本篇学习过程中,我们深入了解了Verilog语言的基本概念、语法和应用,重点提到了Verilog作为硬件描述语言(HDL)与传统编程语言如C语言的差异,我们还详细解析了Verilog的语法,包括逻辑值的表示、常用的数据类型(如寄存器类型reg与线网类型wire)以及多种运算符的使用。

然后讲解了Verilog的模块,Verilog的程序结构由模块(module)构成,每个模块定义了电路的功能和接口。模块的功能可以通过assign语句、always语句或模块实例化来实现。尤其需要注意的是,Verilog中的设计是并行执行的,与传统软件编程语言的串行执行方式不同,这一点也是其在硬件实现中的巨大优势。最后学习了Verilog的三种进阶的语句。

相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
ZPC82105 天前
docker 镜像备份
人工智能·算法·fpga开发·机器人
ZPC82105 天前
docker 使用GUI ROS2
人工智能·算法·fpga开发·机器人
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习