在掌握了 Verilog 的基础语法和常用程序框架之后,本节将带大家深入学习一些 高级设计知识点。这些内容包括:
阻塞赋值(=
)与非阻塞赋值(<=
)的区别及使用场景;
assign
和 always
语句的差异;
什么是锁存器(Latch),以及如何避免不必要的锁存器;
有限状态机(FSM)的基本结构与应用;
模块化设计思想及其实践方法。

posedge clk
:时钟上升沿触发;
negedge rst_n
:低电平复位触发(rst_n 是低有效复位信号);
这表示在复位时,三个变量分别被赋初值。
a = 0;
b = a;
c = b;
这三行是阻塞赋值,意味着它们是顺序执行的,不是并行的。
以 b = a;
举例,由于 a = 0;
已经执行完了,b
实际上赋的值就是 0。
同理,c = b;
也赋的是更新后的 b
,也是 0。
最终
a = 0
,b = 0
,c = 0
如果我们改用非阻塞赋值(<=
):
a <= 0;
b <= a;
c <= b;
这样所有赋值将在时钟沿结束后同时生效,即:
a <= 0;
b <= 原来的 a 值
c <= 原来的 b 值
这就实现了寄存器的移位操作(流水线结构),是我们设计中经常想要的行为。
特性 | 阻塞赋值 (= ) | 非阻塞赋值 (<= ) |
---|---|---|
执行方式 | 按顺序执行(立即更新) | 并行执行(统一在时钟边沿更新) |
用于 | 一般用于组合逻辑 | 推荐用于时序逻辑(寄存器) |
本例中问题 | 变量值被覆盖,行为不符合预期 | 可实现流水线等预期行为 |

这部分内容是非阻塞的内容,在此阻塞的内容也介绍了。
ok完毕~,接下来就是assign 和 always 区别了。
在 Verilog 中,assign
和 always
是两种描述逻辑行为的基本语句。两者既有相似性也有明显的区别,特别是在时序逻辑和组合逻辑的描述中用途不同。
assign 语句
用法: 用于连续赋值(continuous assignment)。
只能用于组合逻辑;
不能包含时钟;
语句写法非常简洁,适用于简单的逻辑关系。
always @(*) begincase (led_ctrl_cnt)2'd0 : led = 4'b0001;2'd1 : led = 4'b0010;2'd2 : led = 4'b0100;2'd3 : led = 4'b1000;default : led = 4'b0000;endcase
end
项目 | assign | always @(*) |
---|---|---|
用于描述 | 连续赋值的组合逻辑 | 过程赋值的组合逻辑或更复杂的逻辑 |
是否必须赋值所有输出 | 是(自动) | 是(手动,否则会生成锁存器) |
写法 | 单行表达式(简洁) | 可多行,逻辑复杂时更清晰 |
可读性 | 简洁,逻辑简单时最直观 | 更灵活,适合多判断、多分支等场景 |
是否需要 default 分支 | 不需要 | 建议加上,否则可能综合成锁存器 |
是否可用于时序逻辑 | ❌ 否(不含时钟,不能生成寄存器) | ✅ 可用于时序逻辑(带 clk) |
项目 | 锁存器(Latch) | 寄存器(Register) |
---|---|---|
触发方式 | 电平触发(高电平或低电平) | 边沿触发(posedge 或 negedge) |
属于哪种逻辑 | 组合逻辑推导而来 | 时序逻辑 |
是否带时钟信号 | ❌ 不依赖时钟 | ✅ 依赖时钟 |
容易产生的问题 | ⚠️ 容易引入毛刺、竞争冒险 | ✅ 稳定,常规使用 |
综合方式 | 在 always @(*) 中未完整赋值会生成 | 使用 always @(posedge clk) 等生成 |
“latch 是组合逻辑中赋值不完整的副产品,既不安全也不推荐,应靠良好编码习惯避免。”
Verilog 是用于描述并行硬件电路的语言,但在某些功能中,我们想按“步骤”来完成逻辑,比如:
串口收发数据(开始 → 收 → 校验 → 存储)
SDRAM 控制(初始化 → 激活 → 读写 → 刷新)
协议处理器(等待命令 → 解码 → 执行)
这时候,大量的 if/else 嵌套 不仅难写,而且容易出错。状态机(FSM)能把这些步骤清晰地“拆分成状态”,通过时钟驱动状态跳转来控制整个流程,逻辑结构清晰、易于维护。
状态机的分类:Mealy vs Moore
类型 | 状态转移是否依赖输入 | 输出是否依赖输入 | 特点 |
---|---|---|---|
Moore | ✅ 是 | ❌ 否 | 输出只依赖状态,更稳定、更容易综合 |
Mealy | ✅ 是 | ✅ 是 | 输出响应更快,但逻辑更复杂 |