type
slug
status
summary
icon
category
date
tags
password
2.1 Verilog HDL概述
2.1.1 Verilog HDL简介
- Verilog HDL是一种硬件语言,最终是为了产生实际的硬件电路或对硬件电路进行仿真。
- 目的: 它的最终目标不是运行在CPU上的软件程序,而是为了:
- 生成实际的硬件电路(通过综合工具,将代码转化为门级网表,再映射到FPGA或 ASIC芯片上)。
- 对硬件电路进行仿真(在电脑上模拟电路的行为,验证功能是否正确)。
- 写Verilog 就是在“画”电路图,只是用文本代码的形式。
注意:编程思维转换:从软件到硬件。
- 利用Verilog HDL编程时,要时刻牢记Verilog是硬件语言,要时刻将Verilog HDL语句与硬件电路对应起来;
- 软件思维:代码是按顺序一行一行执行的(串行)。
- CPU 是单核/多核,但指令是按顺序一条条取指、译码、执行的。
if-else、for循环等控制结构是靠程序计数器(PC)来实现的。
- 硬件思维:电路在物理上是并行工作
- 一旦上电,所有门电路、触发器、连线都在同时工作。信号在导线中传播有延迟,但这个延迟是并行发生的。不存在“先执行 A 再执行 B”这种概念,除非你人为地用时钟去同步它们。
- 要求在Verilog HDL的module中,所有描述语句(包括连续赋值语句assign、行为语句块always/initial、模块实例化)都是并发执行的;
- 电路行为的先后顺序通过时钟节拍顺序来体现。

- 并发执行(Concurrent Execution)
- 这是Verilog的核心特性之一。在一个
module(模块)内部,所有的语句都是并发执行的,它们没有先后顺序(除了在always块内部的顺序语句)。 - 主要包括以下几类语句:
- 连续赋值语句(
assign):用于描述组合逻辑,如assign y = a | b; - 行为语句块(
always,initial):用于描述时序逻辑或初始化行为。 - 模块实例化 (
inst_name uut (...)):实例化一个子模块,相当于在电路图中放置一个子模块。 - 形象比喻:想象一个工厂车间,里面有很多机器(
assign,always, 模块实例),它们都在同时运转,互不干扰。只有当某个机器需要等待“时钟信号”(就像工厂的统一指令)时,它才会在特定时刻动作。
- 表达“先后顺序”?—— 时钟节拍 (Clock Cycle)
- 既然所有语句都是并发的,那怎么表示“先做 A,再做 B”呢?
- 答案:使用时钟信号 (Clock Signal)。
- 数字电路中的“顺序”是通过时钟节拍来控制的。在一个时钟周期内,某些操作完成;下一个时钟周期,再进行下一步操作。

2.1.2 Verilog HDL多种描述方法
Verilog HDL 的四种主要建模方式(描述层次):决定了你如何用代码来“表达”和“构建”一个数字电路。简单来说,这就像用不同的“画笔”或“工具”来描绘同一个电路:你可以从最底层的晶体管开始画,也可以直接画出一个逻辑门,或者用算法来描述它的功能。每种方式都有其适用的场景和优缺点。

- 开关级描述方式(开关级建模)
- 这是最底层的建模方式,直接描述电路中晶体管(MOSFET)和二极管的行为。
- 能够使用内置开关级原语对设计完整建模;开关级基本结构模型:内置pmos、nmos等


- 门级描述方式(门级建模)
- 描述电路是由哪些标准逻辑门(如与门、或门、非门等)构成的。
- 能够使用内置门级原语对设计完整建模,如and、or、nand等

- 数据流级描述方式(数据流级建模)
- 描述数据是如何在电路中流动的,关注的是信号之间的逻辑关系。
- 能够使用内置数据流级原语assign、连续赋值语句和位运算符对设计完整建模;
- 位运算符有:&、︳、~、 ˆ、~ˆ等。
- 这句话的意思是,在写
assign语句时,你应该只使用基本的逻辑运算符和信号名,不要混入always块或其他结构。数据流级描述是纯粹的组合逻辑描述。

“所以在使用数据流级描述的时候,需要特别注意不能用到其他原语或者符号了。”
- 行为级描述方式(行为级建模)
- 最高层级的建模方式,描述的是电路的功能和行为,完全不关心内部结构。
- 能够使用结构和算法对设计完整建模;
- 常用语句有:initial(只执行一次),always (循环执行);还提供一些高级语言结构,如if语句、case语句、循环语句等。

【叽里呱啦什么呢,到时候再回来看看在说什么】
2.2 Verilog HDL基本语法
2.2.1 标识符
- 在Verilog中,标识符就是你用来给各种东西“起名字”的字符串。简单来说,凡是你自己定义的名字,都叫标识符。这些“东西”包括:
- 模块名 (
module my_module) - 信号名/变量名 (
reg clk;,wire data_out;) - 端口名 (
input a, b; output y;) - 参数名 (
parameter WIDTH = 8;) - 实例名 (
my_submod uut (...))
- Verilog HDL中的标识符是由任意的字母、数字、$符和下划线组成的字符序列。
- 相较于高级程序设计语言而言,多了一个$符
- 标识符的第一个字符必须是字母或者下划线,不能以数字或者&开头。
- 大小写敏感:标识符是区分大小写的。例如,count和COUNT是不同的。
- 关键词 (Keywords) ——不能用作标识符的“保留字”
- Verilog定义了一套自己的“关键词”,它们有特殊的含义和功能,不能被用作自己定义的标识符。
- 关键词是Verilog语言中预定义的、具有特定语法功能的单词。
- 例如:
module,endmodule,input,output,reg,wire,assign,always,initial,if,else,case,endcase等等。 - 关键点:只有全部小写的关键词才是真正的“保留字”。
- 这是 Verilog 的一个独特之处!
always是关键词,因为它全小写。ALWAYS不是关键词,因为它全是大写。Always也不是关键词,因为它是大小写混合。

2.2.2 数值、常量、数据类型及变量
- 这部分内容主要回答了两个关键问题:
- Verilog 能表示哪些“值”? (不仅仅是0和1)
- 如何在代码里写这些“值”? (特别是带位宽和进制的常量)
- 🎯 核心概念速览
- 数值(Value): Verilog 中信号可以取的四种基本状态。
- 常量(Constant): 在代码中直接写出的、固定不变的数值。
- 数据类型(Data Type): 用于定义变量(如
reg,wire)能存储什么类型的数据。
2.2.2.1 数值
- Verilog HDL的数值有四类:
- 0(逻辑0)
- 1(逻辑1)
- Z/z(高阻)
- 这里的高阻就是指三态门中的高阻态,也就是相当于断路
- X/x(未知)
- Verilog中的未知不是使用d来表示的

2.2.2.2 常量
- 整数型常量 (Integer Constants)
- 在代码中写数字时最常用的方式。
- 定义格式:<位宽>'<进制><数字>
- <位宽>:指定这个常量占用多少位。例如8表示8位。如果省略位宽,则默认为32位。
- ':单引号,是分隔符。
- <进制>:指定数字的进制。
- b或B:二进制 (Binary)
- o或O:八进制 (Octal)
- d或D:十进制 (Decimal)
- h或H:十六进制 (Hexadecimal)
- <数字>:具体的数值。
- 注意:单引号'和进制字母之间不能有空格。
- 示例
- 关于负数:Verilog不支持在常量定义中直接写负数。
- 因为在硬件层面,“负号”不是一个物理实体。计算机(和FPGA)内部所有的数,都是用一串0和1来表示的。为了表示负数,我们使用“二进制补码”这种编码方式。
- 方式一:直接写出补码
- 确定你需要的位宽(比如4位)。
- 计算该负数的二进制补码。
- 在补码中最高位表示符号位。这个符号位不是独立存在的,它本身就是数值的一部分,参与运算,这就是补码的关键:补码的符号位是“内嵌”的,不是像原码那样单独拿出来的一个标志位。这就是为什么补码能实现加减法统一的原因。
- 判断符号位是1即为负数,对补码按位取反,末位+1,得到的结果就是负数的绝对值,添上负号就得到负数本身。
- 直接用这个二进制数作为常量。
- 方式二:使用s前缀
- 告诉仿真器/综合器:这个数是一个有符号数的补码表示。
- 6’so72 //位宽为6的有符号八进制数111010 //它是十进制下的-6
- 这里计算先不管s,把72当作普通的八进制数对待:72=111010
- 因为加了s前缀,表示这是一个6位的有符号数。判断111010的最高位(符号位)是1,所以它是一个负数。我们又知道这是这个负数的补码,所以我们根据补码得到原来的负数:取反+1为绝对值6,添上负号,所以是十进制下的-6
- 注意:
- x/z在不同进制下的含义
- x或z在十六进制数值中代表4位二进制x或z,在八进制数值中代表3位二进制x或z,在二进制数值中代表1位x或z。
- 位宽不匹配时的处理规则
- 定义的位宽 > 常量指定的位宽(位宽扩展)
- 对于无符号数
- 在数的左边填0补齐
- 例如:
10'b10→实际是10'b0000000010 - 对于有符号数
- 在左边填符号位补齐
- 例如:
8'sb101101→最高位是1,所以左边填充1,结果是8'b11101101 - 这就是所谓的 “符号扩展”,它保证了数值的大小不变。
- 特殊情况:如果常量的最高位是x或z
- 则左边也填充
x或z。 - 例如:
10'bx0x1→左边填充x,结果是10'bxxxxxx0x1 - 定义的位宽 < 常量指定的位宽(位宽截断)
- 当目标变量的位宽比常量本身的位宽小时,Verilog 会自动截断高位,只保留低位。无论常量是有符号还是无符号,都是从右边开始取低位。高位会被丢弃。
计算负数的补码
首先看绝对值部分的二进制(注意位宽),然后所有位取反,然后末位+1得到这个负数的补码。

已知一个负数的补码,求这个负数

快速求补码的办法





- 实数型(小数)常量
- 形式:由十进制计数法或科学计数法的形式组成。

- 字符串型常量
- 形式:由双引号括起来的任意字符序列
- 内部表示:字符串中的每个字符在Verilog内部都被存储为一个8位的ASCII码。例如:”hello”、”abc”
- 在Verilog中同样有转义字符
- \n:表示换行符
- \t:表示制表符
- \\:表示反斜杠(\)本身
- \”:表示双引号字符(”)
- 补充:Verilog 字符串的局限性
- 不可综合: 这是最重要的限制。任何包含字符串的逻辑都无法被综合成硬件。
- 无字符串操作函数:Verilog没有像C或Python 那样的内置字符串处理函数(如
strlen,strcpy,strcat等)。你不能对字符串进行拼接、查找、修改等操作。 - 存储方式: 虽然概念上是一个“字符串”,但在仿真器内部,它通常被处理为一个由
reg [7:0]组成的数组,每个元素存储一个字符的ASCII码。


注意这里的正确显示两个“”
2.2.3 数据类型及变量
数据类型与基本逻辑单元建库有关,由半导体厂家和EDA工具厂家共同提供。Verilog HDL有两大类数据类型
2.2.3.1 线网类型(Nets Type)
- 定义:表示Verilog结构化元件间的物理连线。
- 本质:它没有存储能力,其值完全由驱动它的元件决定。
- 如果驱动元件的值变了,线网的值会立刻跟着变。
- 如果没有驱动元件,它的值就是缺省值
Z(高阻态),表示“悬空”。 - nets型变量指输出始终根据输入变化而更新其值的变量,一般是指硬件电路中的各种物理连接。
- 常用的线网类型:
wire。 - 在Verilog中,wire是最常用、最基础的线网类型。
- 默认类型:模块的输入/输出端口,如果没有显式声明类型,默认就是wire类型。
- 定义格式
[n:0]:指定位宽。如果省略,则默认为1位。- 可以同时定义多个变量。
- 示例
- 如何赋值
- 只能通过
assign语句或模块实例化来赋值。 - 不能在
always或initial块中直接赋值!因为wire是“连线”,它的值是由外部驱动决定的,而不是由程序逻辑“主动”改变的。
assign语句又被称为连续赋值语句,作用是持续不断地将右边表达式的值赋给左边的
wire变量。assign <wire变量名> = <表达式>;

wire类型的变量通常是不能在过程块中赋值的

2.2.3.2 寄存器类型(Register Type)
- 定义:表示一个抽象的数据存储单元。
- 寄存器型变量对应的是具有状态保持作用的电路元件,如触发器、寄存器等。
- 本质:它具有“状态保持”作用,能在两次赋值之间“记住”前一次的值。
- 对应硬件:最终会被综合工具映射到触发器或锁存器上。
- 关键点:
reg不是“寄存器”的字面意思,而是一个“能保存值”的变量。它可以被赋值,也可以在没有新赋值时保持旧值。
- 主要的寄存器类型
- reg型变量
- 定义格式
[n:0]:指定位宽。如果省略,则默认为1位。- 可以同时定义多个变量。
- 示例
- 如何赋值
- 只能在
always或initial块中赋值。 - 不能通过
assign语句赋值!因为reg是“存储单元”,它的值需要通过程序逻辑(如时钟边沿)来“主动”更新,而不是被动地被驱动。 - reg型变量与nets型变量的根本区别
- reg型变量需要被明确地赋值,并且在被重新赋值前一直保持原值。所以在设计中要将reg型变量放在过程块语句(如Initial , always)中赋值。
- 缺省值为未知x:是因为寄存器中总是会存储一个值的,只不过这个值在使用之前可能不知道是多少,也可能是无效的,因此寄存器变量的缺省值为X。
- Integer型变量
- 定义格式
integer是一种寄存器类型,它只能在always或initial块中被赋值。- 它默认是32位有符号数。
- 示例
- 注意:
integer的定义格式和reg非常相似,但它不能带位宽声明。如果你需要指定位宽,应该使用reg类型。



2.2.4 数组
2.2.4.1 数组的声明
- 定义:把多个相同类型的变量组织在一起,通过索引来访问。
- 格式
<数据类型>:可以是reg,wire,integer等。<索引范围>:指定数组的大小和索引方向。例如[7:0]表示8个元素,索引从7到0。
- 特性
- 数组的元素可以是标量(1位)也可以是向量(多位)。
- 数组的维数可以是一维、二维、三维甚至更高维。
- 数组的引用可以针对某一个元素或某一个元素的一部分
- 示例
reg x[11:0];- reg :这部分整体定义了数组元素的类型。
- reg是数据类型。
- 没有[位宽]:reg默认是1位的。
- 所以,数组的每个元素都是1位的reg(标量)。
- x[11:0]:这是数组x的索引范围,表示有12个这样1位的reg元素(从11到0)。
wire [7:0] y[5:0];(这是最常用、最推荐的写法!)- wire [7:0]:这部分整体定义了数组元素的类型。
- wire是数据类型。
- [7:0]是该类型(wire)的位宽。
- 所以,数组中的每一个元素都是一个8位的wire(向量)。
- y[5:0]:这是数组y的索引范围,表示有6个这样的8位wire元素。
reg [23:0] image_rgb[0:1023][0:1023];- 这是一个二维数组。
- reg [23:0]:这部分整体定义了数组元素的类型
- reg是数据类型。
- [23:0]是该类型(wire)的位宽。
- 所以,数组的每个元素都是一个24位的reg向量。
- image_rgb[0:1023][0:1023]:定义了image_rgb数组的维度。
- 第一个[0:1023]:表示有1024行。
- 第二个[0:1023]:表示每行有1024列。
- 总结:一个1024x1024的“表格”,表格里的每个“格子”里都存着一个24位的数据。
reg arrayb[7:0][0:255];- 这也是一个二维数组,但它是“标量”的二维数组。
- reg :这部分整体定义了数组元素的类型
- reg:定义了数组元素的类型。
- 没有[位宽]:reg默认是1位的。所以,数组的每个元素都是1位的reg(标量)。
- arragyb[7:0][0:255]:定义了arrayb数组的维度。
- 第一个[7:0]:表示有8行。
- 第二个[0:255]:表示每行有256列。
- 总结:一个 8x256 的“表格”,表格里的每个“格子”里都存着1bit(0或1)。
wire [7:0] y[5:0]vswire y[5:0][7:0]- 这两种写法最终定义的硬件结构是一样的:都是6个8位的
wire

2.2.4.2 数组的赋值
- 数组的赋值方式与普通变量类似,但需要指定索引。
- Q:我有点奇怪的是为什么有的时候索引写高到低,有的时候写低到高
- A:Verilog本身对位宽方向没有强制规定,
[7:0]和[0:7]都是合法的,但它们代表了不同的“位序约定”。 选择哪一种,更多是工程习惯偏好的问题。在实际工程中,[高:低](降序)是绝对的主流和强烈推荐的标准。 reg [7:0] data;- 降序:data[7]是最高位,data[0]是最低位。
- 索引大的是高位。
- data = 8'b10110000;
- data[7]=1, data[0]=0,符合我们从左到右写二进制数的习惯(左边是高位)。
reg [0:7] data;- 升序:data[0]是最高位,data[7]是最低位。
- 索引小的是高位。
- data = 8'b10110000;
- data[0]=1, data[7]=0,这与我们的直觉相反。
2.2.4.3 数组访问
- 这就是之前所说的另外一种写法的形式:最左和最右定义元素类型,中间是数组的维度
- wire [7:0]:这是最核心的元素类型。它定义了数组中最小单元是一个8位的wire向量。
- [255:0][255:0]:这是数组的维度。
- 第一个[255:0]:表示有256行。
- 第二个[255:0]:表示每行有256列。
- 最终结构:可以把它想象成一个256 x 256的“大表格”,而 表格里的每一个“格子”里都装着一个8位的数据。
- 完整引用一个格子:
threed_array[行索引][列索引]→ 得到一个8位的向量。
- 引用一个格子里的某一位:
threed_array[行索引][列索引][位索引]→ 得到1位。
- 引用一个格子里的某几位:
threed_array[行索引][列索引][位范围]→ 得到一个子向量。
2.3 Verilog HDL的操作符
2.3.1 算术操作符
Verilog支持6中算术运算符:加法(+)、减法(-)、乘法(*)、除法(/)、取模(%)和幂运算(**)
- 算术运算的注意点
- 整数除法(/)将截断结果的小数部分,例如:7/4的运算结果为1。
- 取模(%)操作符求出与第一个操作符符号相同的余数,例如:7%4的运算结果为3,-7%4的运算结果为-3。
- 算术操作符中任意操作数中只要有一位为x或z,则整个运算结果为x
- 例如:’b10x1+’b0111的运算结果为不确定数’bxxxx
- 因为在真实硬件中,如果一个信号是未知的(x) 或高阻的(z),那么基于这个信号进行的任何计算都是不可靠的,所以结果也应该是未知的(x)。
2.3.3.1 算术运算结果的位宽
- 对于纯算术表达式(未赋值给变量):
- 结果的位宽由参与运算的最大操作数的位宽决定。
- 例如:
a[4:0] + b[3:0]→ 结果是5位。
- 在赋值语句中(
=或<=): - 结果的位宽由等号左边目标变量的位宽决定。
- 如果右边计算结果的位宽大于左边,高位会被截断。
- 如果右边计算结果的位宽小于左边,高位会根据变量类型进行填充(无符号填
0,有符号填符号位)。 - a+b本身是一个4位的算术运算(因为a和b都是4位)。当赋值给c时,结果保持4位。当赋值给d时,结果会被扩展到6位(高位填0,因为d是无符号的)。
- 注意:如果a+b的结果超出了4位(比如 15 + 1 = 16),赋值给c时会发生溢出,结果会是0(因为16的二进制是10000,截断后只剩0000)。这在设计中需要特别小心!
2.3.1.2 有符号数和无符号数
(Signed vs Unsigned)
- 无符号数与有符号数的存储


类型 | 存储位置 | 表示方式 |
无符号数 (Unsigned) | wire, reg, 没有标s的基数格式表示的整型数中 | 没有 s标记的基数格式(如 4'b1101) |
有符号数 (Signed) | reg signed, wire signed, integer | 有 s标记的基数格式(如 4'sb1101) |
reg和wire默认是无符号的。如果你想让它们作为有符号数使用,必须显式声明为reg signed 或 wire signed。
- 系统函数
$signed和$unsigned $signed(<expression>):将表达式转换为有符号数。$unsigned(<expression>):将表达式转换为无符号数。- 第一行:s1101为有符号数,为负数,补码是1101,那么取反+1为绝对值0011=3,添上-,即为-3
- 第二行:去掉s即hA,十六进制的A为10.原来是1010,有符号位,负数,即取反+1为绝对值6,添上-,即原来表示-6.
- 注意:这些函数只是改变了解释数据的方式,并不会改变数据本身的二进制表示。
Verilog 提供了两个强大的系统函数,用于在有符号和无符号形式之间进行转换。
2.3.2 关系操作符
Verilog支持4种关系操作符:大于(>)、小于(<)、大于等于(>=)、小于等于(<=)
- 基本行为
- 关系运算符对两个操作数逐位进行比较,其结果为真(值为1)或假(值为0)。
- x/z的传播规则:若操作数中有一位为x或z,则结果为x。
- 这与算术运算符的规则一致,体现了仿真中对“不确定状态”的保守处理。
- 操作数位宽不匹配:位宽扩展
- 如果所有操作数都是无符号数:
- 位宽较小的操作数在高位填
0补齐。 - 如果所有操作数都是有符号数:
- 位宽较小的操作数在高位填符号位补齐。
- 若表达式中有一个操作数是无符号数,则该表达式的其余操作数都被当作无符号数处理。
- 这就解决了上一个注意点中可能存在的问题:一个数是有符号数,一个数是无符号数。而实际上,在任意一个表达式中都是这样的。
2.3.3 等价操作符
Verilog HDL支持4种等价操作符:逻辑相等(= =)、逻辑不等(!=)、全等(= = =)、非全等(! = =)
- 基本行为
- 等价操作符对两个操作数逐位进行比较,对于逻辑相等(= =)和逻辑不等(!=)的比较结果是真(值为1)或假(值为0)。
- 若操作数中有一位为x或z,则结果为x。
- 对于全等(= = =)和非全等(! = =),则是将x或z当作数值(不考虑其物理含义)严格地按字符值进行比较的,因此其结果不是1就是0,没有未知的情况。
- 操作数位宽不匹配:位宽扩展
- 如果所有操作数都是无符号数:
- 位宽较小的操作数在高位填
0补齐。 - 如果所有操作数都是有符号数:
- 位宽较小的操作数在高位填符号位补齐。
- 如果表达式中有一个无符号数,那么所有的数值都将被转换为无符号数
2.3.4 位操作符
Verilog支持5种位操作符:取反(~)、与(&)、或(|)、异或(^)和同或(^~或~^)。
- 基本行为
- 顾名思义是对输入的操作数进行逐位操作(即对应位进行操作)。结果是一个与操作数位宽相同的向量。
- 对有符号数而言,进行位运算的时候也需要包含符号位。
- 操作数位宽不匹配:位宽扩展
- 如果所有操作数都是无符号数:
- 位宽较小的操作数在高位填
0补齐。 - 如果所有操作数都是有符号数:
- 位宽较小的操作数在高位填符号位补齐。
- 如果表达式中存在无符号数的话,那么其余数都将被视为无符号数。
- x/z传播规则(位运算篇)
- 在Verilog的位运算中,只有当一个输出位的结果“无法确定”时,该位才会被置为 x。如果结果是“可以确定”的,即使输入中有x或z,输出位也可能是0或1。
- 这里举个例子:0&x=0,这是因为结果能确定为0;而1&x=x,这是因为结果无法确定。
2.3.5 逻辑操作符
Verilog支持3种逻辑操作符:逻辑与(&&)、逻辑或(||)和逻辑非(!)。
- 基本行为
- 对布尔表达式进行操作,结果是1位的布尔值。
- 如果操作数中没有x或z,则逻辑操作的结果是1位宽的布尔值(0或1);
- 如果操作数中有x或z,则逻辑操作的结果是1位宽的x。
- 逻辑操作符和位操作符不同。逻辑操作符用于连接布尔表达式,而位操作符用于电路信号的连接
重要提醒: 不要混淆
&&和&。前者是逻辑与,后者是位与。
2.3.6 缩减操作符
Verilog支持6种缩减操作符:缩减与(&)、缩减与非(~ &)、缩减或(|)、缩减或非(~ |)、缩减异或(^)、缩减同或(~ ^或^ ~)。
- 基本行为
- 对单个多位操作数的所有位进行操作,最终产生一个1位的结果。
- x/z的传播:如果操作数中有x或z,则结果为x。
- 缩减与(&)
- 只要操作数中任意一位的值为0,则操作的结果便为0;
- 只要操作数中任意一位的值为x或z,则操作的结果便为x;
- 否则其操作结果为1。
- 缩减与非(~&)
- 缩减与的操作结果求反
- 缩减或(|)
- 只要操作数中任意一位的值为1,则操作的结果便为1;
- 只要操作数中任意一位的值为x或z,则操作的结果便为x;
- 否则其操作结果为0。
- 缩减或非(~|)
- 缩减或的操作结果求反
- 缩减异或(^)
- 只要操作数中任意一位的值为x或z,则操作的结果便为x;
- 若操作数中有偶数个1,则操作结果为0
- 若操作数中有奇数个1,则操作结果为1
- 用途: 计算奇偶校验位。
- 缩减同或(~^或者^~)
- 缩减异或的操作结果求反
唯一一个需要注意的点就是,操作数中只要存在x或者z,那么操作结果就是x。
另外这里还需要明确一个运算:~x=x,~z=x
2.3.7 移位操作符
Verilog支持4种移位操作符:逻辑左移(<<)、逻辑右移(>>)、算术左移(<<<)、算术右移(>>>)。
- 移位操作符 (
<<,>>,<<<,>>>) - 左侧操作数:被移位的数据。
- 右侧操作数:移位的次数。
- 总是被视为无符号数
- 如果右侧操作数是x或z,则整个移位结果为x。
- 逻辑移位(
<<,>>) - 对逻辑移位操作符,由于移位而腾空的位总是填0。
- 算数移位(
<<<,>>>) - 左移腾空的位总是填0
- 右移
- 如果位于操作符左侧的操作数是无符号数,则腾空的位总是填0【无符号数的逻辑移位和算术移位是一样的】
- 如果位于操作符左侧的操作数是有符号数,则腾空的位总是填符号位。
2.3.8 条件操作符
条件操作符 (
? :) 是 Verilog 中唯一的三元运算符,它提供了一种简洁的方式来实现“如果...那么...否则...”的逻辑。根据条件表达式的值从两个表达式中选择一个表达式。- 语法格式
- 执行流程:
- 先计算“条件表达式”。
- 如果条件为真(非零),则返回“表达式1”的值。
- 如果条件为假(零),则返回“表达式2”的值。
- 示例
- 先计算条件表达式marks > 18
- 如果
marks > 18为真,则student = grade_a。 - 如果
marks > 18为假,则student = grade_b。
2.3.9 拼接和复制操作符
拼接操作符 (
{}) 用于将多个操作数(信号或常量)“拼接”成一个更宽的操作数。- 语法格式
- 将各个操作数用{}扩起来,每个操作数之间用,隔开
- 所有表达式从左到右依次拼接
- 操作数类型可以是线网类型或者寄存器类型。
- 示例
- 关键特性
- 位宽:拼接后的总位宽等于所有表达式位宽之和。
- 如果需要多次重复拼接同一个操作数,可以使用常数表示需要重复拼接的次数。
- 通常会在需要赋值的操作数上再加上一层花括号。并且这里所说的常数,通常就是直接十进制表示的整数
2.4 Verilog HDL的基本结构
2.4.1 Verilog-HDL语法基础
核心概念总结
Verilog HDL是一种硬件描述语言,用于描述数字电路的结构和行为。它主要有三种建模方式:门级描述数据流级描述和行为级描述。
一个完整的 Verilog 模块通常包含以下几个部分:
- 模块声明 (
module ... endmodule)
- 端口定义 (
input,output,inout)
- 内部信号定义 (
wire,reg)
- 功能描述 (门级实例化、数据流赋值、行为语句)
- 注释
- 多行注释:
/*……*/ - 单行注:
// - 从
//开始到该行结束。
- 标识符
- 定义:用来命名模块、信号、变量等的名字。
- 可以包含英文字母、数字、下划线
_和$符号。 - 首字符不能是数字。
- 大小写敏感。
- module与endmodule
- 这是一对关键字,必须成对出现,用于定义一个模块的开始和结束。所有模块内的代码都必须写在这两者之间。
- input定义输入信号变量
- 用于声明模块的输入端口。
- 输入信号通常是
wire类型(默认),表示它是一个连线,其值由外部驱动。 - 格式:
input [位宽] 信号名; - 例如:
input A, B;(默认是1位) - 或者:
input [7:0] data_in;(8位输入)
- output定义输出信号变量
- 用于声明模块的输出端口。
- 输出信号可以是
wire或reg类型。如果是组合逻辑输出,通常用wire;如果是时序逻辑(如寄存器)输出,则用reg。 - 格式:
output [位宽] 信号名; - 例如:
output F;
- 门级描述
- 这是通过调用Verilo 内置的“门原语”来构建电路。
- 语法:
门类型 实例名 (输出, 输入1, 输入2, ...); - 顺序固定:必须是
(输出, 输入1, 输入2, ...)。 - 实例名:
U1G1等是给这个门实例起的名字,方便调试和引用。 - 内置门原语: 包括
and,nand,or,nor,xor,xnor,buf(缓冲器)。 - 示例
- 这相当于在电路图中放置了一个两输入的与门,输入是 A 和 B,输出是 F。
- 数据流描述
- 这是通过连续赋值语句 (
assign) 来描述组合逻辑。 - 语法:
assign 输出信号 = 表达式; - 表达式中使用的是位运算符,而不是算术运算符。
- 位运算符:
&: 位与 (AND)|: 位或 (OR)~: 位非 (NOT)^: 位异或 (XOR)~^或^~: 位同或 (XNOR)- 优先级:
~(最高) >&>^>~^>|(最低)。复杂表达式建议加括号。 - 示例
endmodule结尾- 最后一行是
endmodule,标志着模块定义的结束。这一行末尾没有分号;。 - module行(首行)有“;”
- 模块内部的大多数语句(如
input,output,assign,and等)末尾必须有分号;。
2.4.2 模块的定义
通用模板
Verilog中有三种基本级别:门级、数据流级和行为级
两种建模方式对比
- 门级描述
- 特点
- 非常直观,直接对应物理电路图。
- 描述的是电路的结构。
- 适合小规模、简单的组合逻辑。
- 优点:易于理解和仿真,与实际硬件对应关系最直接。
- 缺点:对于复杂电路,代码冗长,不易维护。
- 数据流级描述
- 特点
- 描述的是信号之间的数据流动和逻辑关系。
- 更抽象,更接近数学表达式。
- 代码简洁,易于阅读。
- 优点:代码量少,效率高,适合描述复杂的组合逻辑。
- 缺点:不直接反映电路结构,对于初学者可能不如门级直观。
2.4.2 模块的编写练习
1.编写一个2输入或门模块
- 使用门级描述。
- 使用数据流级描述。
2.编写一个2输入异或门模块
- 同样用两种方法。

在Verilog-HDL中,属于内置门实例语句的有:and(与)、nand(与非)、or(或)、nor(或非)、xor(异或)、xnor(同或)、buf(缓冲器)。

在这里用assign来描述电路的逻辑功能,位运算符“&”。位运算符的种类和功能有:&:AND;︳:OR;~:NOT;ˆ:XOR;~ˆ:XNOR
位运算的优先顺序如下:~ > & > ˆ > ~ˆ > |
2.5 Verilog HDL的行为级描述
- 行为级描述
- 行为级描述相当于软件设计过程中的流程图描述或算法描述,它抽象地表达电路功能的行为表现,而不是具体的实现手段和方法。
- 核心思想:你告诉Verilog“这个电路应该做什么”,而不是“它内部的门电路怎么连接”。
- 优势:非常适合描述复杂的时序逻辑电路(如状态机、计数器、CPU控制器等),代码简洁,易于理解和修改。
- 行为级描述的核心:过程块
- 行为级描述主要通过两种过程块来实现:
initial过程块always过程块- 重要前提:只有寄存器型数据(
reg)能在过程块中被赋值 wire(线网)类型的信号是“被动”的,它的值由驱动它的元件(如门、assign语句)决定。reg类型的信号是“主动”的,它的值可以由过程块中的语句主动改变,并且能保持住当前值,直到下一次被赋值。这正是时序逻辑(如触发器)所需要的特性。- 所有的
initial和always过程块在仿真开始的0时刻就会并发执行(同时启动)。
2.5.1 赋值操作
Verilog 有两种基本的赋值操作,它们的应用场景完全不同:
赋值类型 | 关键字 | 适用对象 | 特点 |
连续赋值 | assign | wire (线网) | 持续驱动,只要右边表达式变化,左边就立即更新。用于组合逻辑。 |
过程赋值 | = 或 <= | reg (寄存器) | 在过程块内执行,受控制语句(如 if, case, always @)影响。用于时序逻辑和复杂组合逻辑。 |
- 连续赋值 (
assign) - 用途: 专门用于对
wire类型的信号进行赋值。 - 特点: 它是一种“并行”的、持续的赋值。只要其右侧的表达式发生变化,左侧的
wire就会立即更新。 - 例子
- 这是一个三元条件运算符,如果enable为真,则mynet=data;否则等于 0。这是一个典型的组合逻辑。
- 过程赋值
- 用途: 专门用于对
reg类型的信号进行赋值。 - 特点
- 把值放在寄存器中,过程赋值没有持续时间,相反,寄存器将保持赋值的值,直到发生下一次对变量的赋值。
- 过程赋值发生在过程块(always,initial,task和function)中,可以把它认为是触发器赋值(因为它的执行依赖于特定的事件(如时钟上升沿))。当执行到达过程块的赋值时,触发就发生。
- 受执行语句控制,事件控制、延迟控制、if语句、case语句和循环语句都能用来控制是否执行赋值操作。
总结一下,就是线网类型的变量只能使用assign进行赋值,并且不能使用在过程块中;而寄存器类型的变量只能在过程块中赋值;
那么就是在使用数据流级进行描述的时候,就只能使用位操作符和assign连续赋值语句;但是在使用行为级进行描述时,既可以使用过程块和过程性赋值语句,也可以在过程块外使用assign连续赋值语句。
2.5.2 过程块结构的定义
- initial过程块
- 定义
- 特点:
- 在仿真时间0时刻开始执行。
- 只执行一次,执行完毕后就“死亡”了。
- 用途:
- 给寄存器变量赋初始值。
- 编写测试激励(Testbench),模拟输入信号的变化。
- always过程块
- 定义
- 特点:
- 在仿真时间0时刻开始执行。
- 无限循环,反复执行。
- 它的执行不是无时无刻都在进行,而是只有当敏感事件表中的信号发生变化时才会被触发执行。
- 用途: 描述时序逻辑(如D触发器、计数器)和复杂的组合逻辑。
- 敏感事件表 (
@(...)) @(posedge clk):当clk的上升沿到来时触发。@(negedge clk):当clk的下降沿到来时触发。@(a or b or c):当ab,或c中任意一个信号发生变化时触发。(常用于组合逻辑)@(*):自动推断敏感列表,包含该always块内所有读取到的信号。(推荐用于组合逻辑,避免遗漏)
需要注意的是,在格式上,initial和always关键字后面是没有分号的;而在执行时间上initial过程块和always过程块都是在0时刻开始并发执行的(assign连续赋值语句也在并发执行)
2.5.3 顺序语句与阻塞/非阻塞赋值
可以理解为:begin-end就是程序设计语言中语句块的花括号。因此当always、initial、分支语句中所包含的语句超过一句的时候,就需要使用begin-end将语句包括起来了。
在
initial和always过程块内部,你可以使用类似软件编程的顺序结构、分支结构和循环结构。- 顺序结构 (
begin-end) - 使用
begin ... end来将多条语句组织成一个语句块。 - 在
begin-end块内的语句是按顺序执行的。
在顺序块中出现的语句都是过程性赋值语句,过程性赋值语句有两种类型:
类型 | 符号 | 执行方式 | 适用场景 | 关键区别 |
阻塞赋值 | = | 顺序执行。前一条语句必须执行完毕,才能执行下一条。 | 组合逻辑 | 适用于描述“先算A,再用A算B”的顺序逻辑。 |
非阻塞赋值 | <= | 并行执行。所有赋值操作“同时”安排在未来某个时刻完成,然后继续执行下一条语句。 | 时序逻辑 | 适用于描述“在同一时钟沿,多个寄存器同时更新”的情 |
非阻塞赋值即并不等到前式赋值完成后才执行下一句。而阻塞过程性赋值是一直等到前式被赋了新值后才执行下一句。
这段代码展示了阻塞赋值 (
=)和非阻塞赋值(<=)在结合延迟控制(#)时的行为差异。Z

从上面这个例子就能看出,阻塞赋值一定要等到前面的赋值语句执行结束,因此如果使用绝对时间规定赋值语句的执行时间就不一定有意义了(因为绝对时间并不一定能被满足),因此是使用相对时间来指定赋值语句执行的时间;而非阻塞赋值由于赋值语句之间没有同步的要求,因此会直接使用绝对时间来指定赋值语句的执行时间。
2.5.4 分支语句
这两者是 Verilog 中实现条件判断的主要手段,类似于软件编程中的
if-else和switch-case。if_else语句- 语法
- 特点
- 按顺序检查条件,一旦某个条件为真,就执行对应的语句,然后跳出整个
if-else结构。 - 最后的
else是可选的,用于处理所有条件都不满足的情况。
这里的条件表达式需要进行连接的时候,需要使用逻辑操作符(与&&、或||、非!,与程序设计语言中的运算符是相同的),而不是位操作符(与&、或|、非~、异或^、同或~^/^~)。
在格式上还需要注意的是,if关键字和后面的条件表达式之间有一个空格。
case语句- 语法
- 特点
case语句会将控制表达式的值与各个分支项表达式进行精确匹配。- 一旦找到匹配项,就执行对应的语句,然后跳出
case结构。 default分支是可选的,用于处理所有分支项都不匹配的情况。- 适用场景: 当需要根据一个信号的不同取值来选择不同的操作时,如状态机、数据选择器等。它比
if-else更清晰、更高效。
- 4选1数据选择器的两种实现
- 这是一个经典的组合逻辑电路,它的功能是根据地址信号
A[1:0]从四个输入D0, D1, D2, D3中选择一个输出到Y。


实现一:使用
case语句注意: 这里使用的是阻塞赋值
=,因为这是一个组合逻辑电路,没有时钟,也不需要保持状态。实现二:使用
if_else语句2.6 习题

- 1. 硬件描述语言主要有哪两种?
- Verilog HDL: 语法风格类似 C 语言,简洁明了,应用非常广泛。
- VHDL (VHSIC Hardware Description Language): 语法风格类似 Ada 语言,结构严谨,类型系统更复杂,常用于军事和航空航天等高可靠性领域。
答:
硬件描述语言(HDL)主要有两种:
- 2. Verilog提供了哪几种描述方式?
答:
Verilog 提供了三种基本的描述方式(建模级别):
- 门级描述 (Gate-Level Modeling): 通过调用内置的逻辑门原语(如
and,or,not等)来描述电路的物理结构。
- 数据流级描述 (Dataflow-Level Modeling): 使用连续赋值语句
assign和位运算符(如&,|,^等)来描述信号之间的逻辑关系。
- 行为级描述 (Behavioral-Level Modeling): 使用过程块(
initial,always)和高级语句(如if-else,case,for等)来描述电路的功能和行为,而不关心具体的实现细节。
- 3. Verilog HDL的数值有哪4类?
答:
Verilog 中的数值(或称“四值逻辑”)有以下四种状态:
- 0: 逻辑低电平。
- 1: 逻辑高电平。
- x (或 X): 未知状态。通常表示信号未被驱动、冲突或初始化前的状态。
- z (或 Z): 高阻态。通常表示三态门处于关闭状态,输出端与电路断开连接。
这四个值可以用来模拟真实数字电路中的各种电气状态,对于仿真和调试非常重要。
- 4. Verilog HDL有两大类数据类型?
答:
Verilog HDL 主要有两大类数据类型:
- 线网型 (Net Type):
- 最常用的是
wire。 - 代表电路中的物理连线,其值由驱动它的元件决定。
- 不能存储值,必须被持续驱动。
- 用
assign语句或门原语进行赋值。
- 寄存器型 (Register Type):
- 最常用的是
reg。 - 代表存储单元(如触发器、锁存器),能够保持其值。
- 只能在过程块(
initial,always)中被赋值。 - 即使没有时钟,也可以在组合逻辑中使用
reg类型(此时它只是一个变量,不代表真正的寄存器)。
关键区别: wire 是“被动”的,reg 是“主动”的。
- 5. Verilog的模块结构是什么?
答:
一个完整的 Verilog 模块结构通常包含以下几个部分:
module ... endmodule: 定义模块的开始和结束。
- 端口列表: 定义模块与外界交互的接口。
- 内部信号: 声明模块内部使用的信号。
- 功能描述: 描述模块的具体逻辑功能。
- 6. Verilog HDL的行为级描述可以使用过程块结构进行描述,过程块结构有哪两种?
答:
行为级描述主要通过以下两种过程块结构进行:
initial过程块:- 在仿真时间
0时刻开始执行,并且只执行一次。 - 常用于给寄存器变量赋初值或编写测试激励(Testbench)。
always过程块:- 在仿真时间
0时刻开始执行,并且无限循环。 - 通过敏感事件表(如
@(posedge clk)或@(*))来控制何时触发执行。 - 是描述时序逻辑和复杂组合逻辑的核心。
- 7. 在顺序块中出现的语句都是过程性赋值语句,过程性赋值语句有两种类型?请解释这两种类型。
答:
过程性赋值语句有两种类型:
- 阻塞式赋值 (Blocking Assignment):
- 使用符号
=。 - 特点: 赋值操作是顺序执行的。当前语句执行完毕(即左边变量被赋予新值)后,才会执行下一条语句。
- 适用场景: 主要用于描述组合逻辑。
- 形象比喻: 就像排队,前一个人走完了,后一个人才能开始。
- 非阻塞式赋值 (Non-blocking Assignment):
- 使用符号
<=。 - 特点: 赋值操作是并发执行的。所有赋值语句会“同时”计算右边的表达式,并将结果安排在当前仿真时间点之后(通常是下一个时间步)更新到左边的变量上,然后立即继续执行下一条语句。
- 适用场景: 主要用于描述时序逻辑(如触发器、计数器)。
- 形象比喻: 就像多车道的十字路口,绿灯亮起时,所有方向的车都“同时”获得通行权。
- 8. 阻塞式赋值和非阻塞式赋值的区别?
答:
两者的主要区别在于执行顺序和赋值时机:
特性 | 阻塞式赋值 ( =) | 非阻塞式赋值 ( <=) |
执行方式 | 顺序执行 | 并发执行 |
赋值时机 | 立即赋值 | 计划在未来某个时刻赋值 |
对后续语句的影响 | 必须等待当前语句执行完毕才能执行下一句 | 不等待,立即执行下一句 |
典型应用场景 | 组合逻辑 | 时序逻辑 |
核心原则: 在同一个 always 块中,应避免混合使用 = 和 <=,以免产生难以预料的综合结果。
- 9. 判断:CASE语句是最重要最常用的顺序语句?
答:错误。
- 理由: 虽然
case语句在描述多路选择器、状态机等场景时非常常用和高效,但它并非“最重要最常用”的顺序语句。
- 最常用的顺序语句应该是
if-else语句,因为它更通用,几乎可以处理任何条件判断。
case语句的优势在于其结构清晰、易于阅读,特别适合于“多分支选择”的情况,但if-else的灵活性更高。
- 此外,
for循环、while循环等也是重要的顺序语句。
- 10. 判断:标识符 “9moon” 是不合法的标识符?
答:正确。
- 理由: 根据 Verilog 的语法规则,标识符(如模块名、信号名)不能以数字开头。
- “9moon” 以数字
9开头,因此是一个不合法的标识符。
- 合法的标识符可以是:
moon9,my_moon,_moon,Moon等。
希望这份详细的解答能帮助你彻底掌握本章的知识点!祝你学习顺利!
- 作者:Sparkle_Yuyu
- 链接:https://imyuyu.top/article/DigLogic/Chapter2
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
