前言
本文介绍了 Verilog 组合逻辑和时序逻辑。
操作系统:Windows 11 家庭中文版
信号
时钟信号(Clock Signal)
时钟信号的名称
在Verilog设计中,时钟信号一般用作电路的时序驱动,用于触发寄存器、状态机等时序逻辑模块。
时钟信号通常在Verilog代码中以
clk
、clock
等名字出现。例如:input clk; // clk就是时钟信号的名称
时钟信号的频率
时钟信号是一个周期性的方波信号,频率决定了电路中数据变化或寄存器更新的速度。
在设计文档或代码注释中,需要说明时钟信号的频率,比如“时钟频率为50MHz”。
代码层面通常不会直接描述频率,但在仿真(testbench)中会用#延时来模拟,例如:
// 50MHz时钟周期为20ns always #10 clk = ~clk; // 每10ns翻转一次,周期为20ns,频率为50MHz
重置信号(Reset Signal)
重置信号的名称
重置信号用于初始化或清零电路状态。
常见的名字有
rst
、reset
、rst_n
、reset_n
等。例如:input rst_n; // rst_n为重置信号的名称
重置类型
重置信号的类型通常有两种:
- 同步复位(Synchronous Reset):在时钟沿到来时,判断重置信号是否有效。如果有效,再做初始化操作。
- 异步复位(Asynchronous Reset):不依赖时钟信号,只要复位信号有效,立即初始化。
另外,重置信号还分为高有效和低有效两种:
- 高有效(Active High):重置信号为
1
时有效(如reset
)。 - 低有效(Active Low):重置信号为
0
时有效(如reset_n
、rst_n
,末尾的_n
表示低有效)。
- 高有效(Active High):重置信号为
代码示例:
同步低有效复位:
always @(posedge clk) begin if (!rst_n) // 低有效 q <= 0; else q <= d; end
异步高有效复位:
always @(posedge clk or posedge reset) begin if (reset) // 高有效 q <= 0; else q <= d; end
组合逻辑和时序逻辑
组合逻辑(Combinational Logic)
概念
- 组合逻辑是一种没有记忆能力的电路逻辑。
- 其输出仅依赖于当前输入,输入信号变化马上影响输出。
- 没有时钟信号的参与。
典型例子
- 与门(AND)、或门(OR)、非门(NOT)
- 加法器、比较器、多路选择器等
Verilog描述方式
- 使用
assign
语句,或者在always @(*)
块中描述。 - 没有触发器(如D触发器)。
组合逻辑Verilog代码示例
// 2输入与门
assign y = a & b;
// 4选1多路选择器
always @(*) begin
case (sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
2'b11: y = d;
endcase
end
时序逻辑(Sequential Logic)
概念
- 时序逻辑是一种有记忆能力的电路逻辑。
- 其输出不仅依赖当前输入,还依赖于以往的输入(历史状态)。
- 需要时钟信号或重置信号参与。
典型例子
- 寄存器、触发器、计数器、状态机等
Verilog描述方式
- 使用
always @(posedge clk)
或always @(negedge clk)
等时钟沿敏感的块。 - 通常用于描述同步逻辑(同步时钟驱动),并可加入复位逻辑。
时序逻辑Verilog代码示例
// D触发器,有同步复位
always @(posedge clk) begin
if (rst) // rst为高有效复位信号
q <= 0;
else
q <= d;
end
// 简单的二进制计数器
always @(posedge clk or posedge rst) begin
if (rst)
count <= 0;
else
count <= count + 1;
end
区别与联系
项目 | 组合逻辑 | 时序逻辑 |
---|---|---|
是否有记忆 | 无 | 有 |
输出依赖 | 仅当前输入 | 当前输入+历史状态 |
时钟参与 | 否 | 是 |
例子 | 与非门、多路选择器 | 寄存器、计数器、状态机 |
Verilog描述 | assign、always @(*) | always @(posedge/negedge clk) |
联系
- 时序逻辑的实现通常依赖组合逻辑和触发器的共同作用。
- 例如,状态机的状态转移部分是组合逻辑,状态寄存则是时序逻辑。
总结
- 组合逻辑:“现在的输入决定现在的输出”,没有记忆能力。
- 时序逻辑:“现在的输入+过去的状态决定现在的输出”,有记忆能力,需要时钟或复位信号。
D触发器
下面详细介绍一下D触发器(D Flip-Flop),包括它的原理、功能、符号、应用以及在Verilog中的实现方法。
基本概念
D触发器是一种最基本的时序逻辑存储单元,用于存储1位二进制信息。
“D”代表“Data”或“Delay”,也叫做数据触发器或延迟触发器。
工作原理
- 输入端:有
D
(数据输入)、CLK
(时钟输入)。 - 输出端:
Q
(输出),有时还有Q̅
(Q的反相)。 - 功能:在时钟信号的特定沿(通常为上升沿
posedge
或下降沿negedge
)到来时,将D
端的数值锁存到Q
输出端,并保持直到下一个时钟沿到来。 - 记忆能力:D触发器只在时钟的有效沿采样并保存输入,其余时间输出不变。
真值表
时钟 | D | Q(下一个时刻) |
---|---|---|
↑ | 0 | 0 |
↑ | 1 | 1 |
其他 | X | Q(保持原值) |
(↑ 表示时钟上升沿,X表示无关)
逻辑符号(图示)
+-----+
D ---| |--- Q
| DFF |
CLK --| |
+-----+
典型应用
- 作为寄存器的基本单元,用来存储数据
- 构建计数器、移位寄存器
- 实现同步状态机的状态寄存
- 用于边沿检测、数据同步等
Verilog实现
最常见的同步D触发器代码:
// 同步复位的D触发器
always @(posedge clk) begin
if (rst) // rst为高有效同步复位
q <= 0;
else
q <= d;
end
带异步复位的D触发器:
// 异步复位的D触发器
always @(posedge clk or posedge rst) begin
if (rst) // rst为高有效异步复位
q <= 0;
else
q <= d;
end
只有时钟,无复位的D触发器:
always @(posedge clk) begin
q <= d;
end
- 其中,
clk
是时钟信号,rst
是复位信号,d
是输入数据,q
是输出数据。
总结
- D触发器是最基本的时序逻辑单元,工作在时钟同步下,用来存储1位数据。
- 只在时钟有效沿采样输入,输出保持稳定,具有“记忆”能力。
- 是寄存器、状态机等复杂时序电路的基础单元。
解释=和<=
下面详细解释一下在Verilog中=
和<=
的区别,这也是Verilog初学者和实际设计中非常重要的知识点。
=
和 <=
的基本区别
=
:阻塞赋值(Blocking Assignment)
英文名:Blocking Assignment
使用场合:一般用于组合逻辑(如
always @(*)
块中),也可用于仿真中的临时变量赋值。执行方式:顺序执行。等号左边的变量会立即被赋值,后续语句能看到这个改变,和C语言赋值类似。
语法示例:
a = b + c; d = a + 1; // 这里a已经是新值了
常见用法:
always @(*) begin y = a & b; z = y | c; // 此时z用到的y,已经是上一句赋的新值 end
<=
:非阻塞赋值(Non-blocking Assignment)
英文名:Non-blocking Assignment
使用场合:一般用于时序逻辑(如
always @(posedge clk)
块中)。执行方式:并行执行。所有
<=
赋值的目标变量都会在本时钟周期的末尾同时更新,赋值语句不会阻塞后面的语句。语法示例:
always @(posedge clk) begin a <= b + c; d <= a + 1; // 此时a还是旧值,d用到的是a的旧值 end
常见用法:
always @(posedge clk) begin q <= d; // 时钟沿到来时,所有q的赋值同时生效 end
实际设计中的区别
组合逻辑建议用=
- 组合逻辑(如
always @(*)
)建议使用=
,因为赋值顺序很重要,且不需要时序更新。
时序逻辑建议用<=
- 时序逻辑(如
always @(posedge clk)
)必须使用<=
,这样才能模拟真实硬件中所有寄存器在同一时钟沿“同时”更新。 - 用
=
写时序逻辑可能会导致仿真与综合不一致,引发难以发现的bug。
举例说明
例1:阻塞赋值(=
)在组合逻辑
always @(*) begin
a = b;
c = a;
end
- 这里
c
会等于b
,因为a
已经被赋值。
例2:非阻塞赋值(<=
)在时序逻辑
always @(posedge clk) begin
a <= b;
c <= a;
end
- 这里
c
会等于前一个时钟周期的a(即a的旧值),因为所有赋值在同一时钟沿后一起生效。
总结口诀
- 组合逻辑用
=
,时序逻辑用<=
。 - 阻塞赋值顺序执行,非阻塞赋值“同时”执行。
- 时序逻辑用
=
会出错,组合逻辑用<=
容易误会。
补充说明
- 在同一个
always
块中混用=
和<=
会导致代码难以理解,甚至出现bug,建议不要混用。 - 仿真时,
=
和<=
的行为差别也会体现在信号波形上。
汇总表格
赋值符号 | 名称 | 场景 | 行为 | 典型块 |
---|---|---|---|---|
= | 阻塞赋值 | 组合逻辑 | 顺序执行 | always @(*) |
<= | 非阻塞赋值 | 时序逻辑 | 并行(同时)执行 | always @(posedge clk) |
组合逻辑和时序逻辑谁是顺序执行?
组合逻辑和时序逻辑的执行方式
组合逻辑
- 本质:没有记忆功能,只有输入输出映射。
- 执行方式:并行(或同时)执行。组合逻辑的输出在输入发生变化后马上同步变化,没有“先后顺序”。
- Verilog实现:常用
assign
或always @(*)
,通常用阻塞赋值(=)。- 例:
assign y = a & b;
- 例:
- 在硬件中,组合逻辑电路的所有部分实际上是并行工作的。
时序逻辑
- 本质:有记忆功能,依赖时钟(clk)沿来驱动。
- 执行方式:按时钟节拍顺序执行,也就是“时序执行”。
- 只有在时钟的上升沿或下降沿时,电路的状态才会更新。
- Verilog实现:用
always @(posedge clk)
/always @(negedge clk)
,用非阻塞赋值(<=)。- 例:
always @(posedge clk) q <= d;
- 例:
- 在硬件中,时序逻辑只有在时钟沿到来时才“执行一次”,更新寄存器/触发器的内容。
顺序执行和时序执行
- 如果你理解“顺序执行”为依次、按顺序执行,在Verilog中主要指阻塞赋值(=)的语句顺序执行,这通常出现在组合逻辑的描述中。
- 如果你理解“时序执行”为依赖时钟的节拍来同步执行,那就是时序逻辑的特征。
结论
- 组合逻辑:在硬件上是并行执行,在Verilog代码块内部可以用
=
实现语句顺序执行,但这是代码描述方式,不代表硬件行为有先后顺序。 - 时序逻辑:是按时钟节拍(时序)执行,所有寄存器/触发器在同一个时钟沿“同时”更新数据。
常见混淆
- 有些初学者容易把Verilog代码的语句“写的顺序”误认为硬件有先后,其实组合逻辑电路是并行的,所有输入一变输出就变,没有先后。
- 时序逻辑才会体现“时序性”,即每个时钟沿发生一次状态更新,这种“节拍”决定了时序逻辑的先后性。
总结
- 如果问:“谁是按时序(时钟)执行的?”——时序逻辑。
- 如果问:“谁的代码是顺序执行的?”——组合逻辑常用阻塞赋值
=
,语句顺序执行,但硬件上还是并行逻辑。 - 真正体现“时序、节拍”特征的是时序逻辑。
代码的顺序
在Verilog中,哪些代码可以任意打乱撰写顺序(即代码的先后顺序不会影响最终硬件功能)?
组合逻辑代码的顺序
组合逻辑(通常用assign
或always @(*)
)
核心点:
在assign
语句,以及always @(*)
块中使用阻塞赋值(=
)描述的组合逻辑,代码的先后顺序在硬件实现时是可以打乱的,因为它们最终描述的都是电路的连线关系,所有组合逻辑是并行实现的。
示例1:assign 语句
assign y = a & b;
assign z = y | c;
你可以写成:
assign z = y | c;
assign y = a & b;
无论顺序如何,综合出来的电路都是一样的。
示例2:always @(*) 块的组合逻辑
always @(*) begin
y = a & b;
z = y | c;
end
你可以写成:
always @(*) begin
z = y | c;
y = a & b;
end
对于功能来说,这样写是没问题的,但不推荐,因为如果z依赖y的赋值,顺序会影响仿真值(即y还没赋值时z就用它了),但不会影响最终综合后的硬件连线。
时序逻辑代码的顺序
时序逻辑(如always @(posedge clk)
)
在always @(posedge clk)
块中,所有用非阻塞赋值(<=
)的语句,其顺序可以任意打乱,不会影响硬件功能。
示例
always @(posedge clk) begin
a <= b;
c <= d;
e <= f;
end
你可以写成:
always @(posedge clk) begin
e <= f;
c <= d;
a <= b;
end
结果完全一样,因为所有<=
赋值都是在时钟沿来时同时更新的。
特殊说明
- 在组合逻辑的
always @(*)
块内,如果你用阻塞赋值(=),变量有依赖关系时,顺序会影响仿真结果(但综合硬件最终是并行的,只要组合逻辑能穷举清楚)。比如:
always @(*) begin a = b; c = a; end
和
always @(*) begin c = a; a = b; end
仿真值会不一样,但硬件综合后,只要赋值关系唯一,结果一样。
- 在时序逻辑的
always @(posedge clk)
块内,非阻塞赋值(<=)语句可以随意打乱顺序,但阻塞赋值(=)不行,用阻塞赋值会导致仿真和综合都出错。
总结表格
代码类型 | 赋值方式 | 顺序能否任意打乱 | 推荐写法 | 说明 |
---|---|---|---|---|
assign | assign | 可以 | 任意 | 纯组合逻辑,描述电路连接关系 |
always @(*) | = | 基本可以(无依赖) | 有依赖时按依赖顺序 | 顺序影响仿真,不影响最终硬件 |
always @(posedge clk) | <= | 可以 | 任意 | 全部在同一时钟沿“同时”赋值 |
总结
- assign语句、always块中用
<=
的时序逻辑代码,其语句顺序可以任意打乱,不影响硬件功能和综合结果。 - always @(*)中用
=
的组合逻辑代码如果变量有依赖,建议按逻辑顺序写,否则仿真结果会错,虽然综合硬件没问题。 - 实际写代码时,养成规范化书写的习惯,便于阅读和维护。
结语
第三百二十八篇博文写完,开心!!!!
今天,也是充满希望的一天。