Verilog 语法 (二)

在掌握了 Verilog 的基础语法和常用程序框架之后,本节将带大家深入学习一些 高级设计知识点。这些内容包括:

阻塞赋值(=)与非阻塞赋值(<=)的区别及使用场景;

assignalways 语句的差异;

什么是锁存器(Latch),以及如何避免不必要的锁存器;

有限状态机(FSM)的基本结构与应用;

模块化设计思想及其实践方法。

阻塞赋值
阻塞赋值,顾名思义即在一个 always 块中,后面的语句会受到前语句的影响,具体来说就是在同一个 always 中,一条阻塞赋值语句如果没有执行结束,那么该语句后面的语句就不能被执行,即被 " 阻塞 " 。也就是说 always 块内的语句是一种顺序关系,这里和 C 语言很类似。符号" = "用于阻塞的赋值(如 :b = a;),阻塞赋值" = "在 begin 和 end 之间的语句是顺序执行,属于串行语句。
RHS :赋值等号右边的表达式或变量可以写作 RHS 表达式或 RHS 变量;
LHS :赋值等号左边的表达式或变量可以写作 LHS 表达式或 LHS 变量;
阻塞赋值的执行可以认为是只有一个步骤的操作,即计算 RHS 的值并更新 LHS ,此时不允许任何其他语句的干扰,所谓的阻塞的概念就是值在同一个 always 块中,其后面的赋值语句从概念上来讲是在前面一条语句赋值完成后才执行的。

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 值

这就实现了寄存器的移位操作(流水线结构),是我们设计中经常想要的行为。

特性 阻塞赋值 (=) 非阻塞赋值 (<=)
执行方式 按顺序执行(立即更新) 并行执行(统一在时钟边沿更新)
用于 一般用于组合逻辑 推荐用于时序逻辑(寄存器)
本例中问题 变量值被覆盖,行为不符合预期 可实现流水线等预期行为

非阻塞赋值
符号" <= "用于非阻塞赋值(如 :b <= a; ),非阻塞赋值是由时钟节拍决定,在时钟上升到来时,执行赋值语句右边,然后将 begin-end 之间的所有赋值语句同时赋值到赋值语句的左边,注意:是 begin---end之间的所有语句,一起执行,且一个时钟只执行一次,属于并行执行语句。这个是和 C 语言最大的一个差异点,大家要逐步理解并行执行的概念。
非阻塞赋值的操作过程可以看作两个步骤:
( 1 )赋值开始的时候,计算 RHS ;
( 2 )赋值结束的时候,更新 LHS 。

这部分内容是非阻塞的内容,在此阻塞的内容也介绍了。

ok完毕~,接下来就是assign 和 always 区别了。

assign always 区别

在 Verilog 中,assignalways 是两种描述逻辑行为的基本语句。两者既有相似性也有明显的区别,特别是在时序逻辑和组合逻辑的描述中用途不同。

assign 语句

用法: 用于连续赋值(continuous assignment)。

只能用于组合逻辑

不能包含时钟

语句写法非常简洁,适用于简单的逻辑关系。

复制代码
always @(*) begin
  case (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 是指锁存器,是一种对脉冲电平敏感的存储单元电路。锁存器和寄存器都是基本存储单元,锁存器是电平触发的存储器,寄存器是边沿触发的存储器。两者的基本功能是一样的,都可以存储数据。锁器是组合逻辑产生的,而寄存器是在时序电路中使用,由时钟触发产生的。
latch 的主要危害是会产生毛刺( glitch ),这种毛刺对下一级电路是很危险的。并且其隐蔽性很强,不易查出。因此,在设计中,应尽量避免 latch 的使用。
代码里面出现 latch 的两个原因是在组合逻辑中, if 或者 case 语句不完整的描述,比如 if 缺少 else 分支,case 缺少 default 分支,导致代码在综合过程中出现了 latch 。解决办法就是 if 必须带 else 分支, case 必须带 default 分支。
大家需要注意下,只有不带时钟的 always 语句 if 或者 case 语句不完整才会产生 latch ,带时钟的语句 if 或者 case 语句不完整描述不会产生 latch 。
下面为缺少 else 分支的带时钟的 always 语句和不带时钟的 always 语句,通过实际产生的电路图可以看到第二个是有一个 latch 的,第一个仍然是普通的带有时钟的寄存器。

项目 锁存器(Latch) 寄存器(Register)
触发方式 电平触发(高电平或低电平) 边沿触发(posedge 或 negedge)
属于哪种逻辑 组合逻辑推导而来 时序逻辑
是否带时钟信号 ❌ 不依赖时钟 ✅ 依赖时钟
容易产生的问题 ⚠️ 容易引入毛刺竞争冒险 ✅ 稳定,常规使用
综合方式 always @(*) 中未完整赋值会生成 使用 always @(posedge clk) 等生成

"latch 是组合逻辑中赋值不完整的副产品,既不安全也不推荐,应靠良好编码习惯避免。"

状态机
Verilog 是用于描述并行硬件电路的语言,但在某些功能中,我们想按"步骤"来完成逻辑,比如:

串口收发数据(开始 → 收 → 校验 → 存储)

SDRAM 控制(初始化 → 激活 → 读写 → 刷新)

协议处理器(等待命令 → 解码 → 执行)

这时候,大量的 if/else 嵌套 不仅难写,而且容易出错。状态机(FSM)能把这些步骤清晰地"拆分成状态",通过时钟驱动状态跳转来控制整个流程,逻辑结构清晰、易于维护。

状态机的分类:Mealy vs Moore

类型 状态转移是否依赖输入 输出是否依赖输入 特点
Moore ✅ 是 ❌ 否 输出只依赖状态,更稳定、更容易综合
Mealy ✅ 是 ✅ 是 输出响应更快,但逻辑更复杂

相关推荐
Cao1234567893211 小时前
FPGA时钟设计
fpga开发
JNTeresa4 小时前
锁存器知识点详解
fpga开发
Cao1234567893217 小时前
FPGA基础之基础语法
fpga开发
一大Cpp7 小时前
通过Quartus II实现Nios II编程
fpga开发
边缘计算社区20 小时前
FPGA与边缘AI:计算革命的前沿力量
人工智能·fpga开发
S&Z346321 小时前
[官方IP] Shift RAM
网络协议·tcp/ip·fpga开发
S&Z34631 天前
[FPGA Video IP] Video Processing Subsystem
网络协议·tcp/ip·fpga开发·video
FPGA_Linuxer1 天前
FPGA 100G UDP纯逻辑协议栈
网络协议·fpga开发·udp
Terasic友晶科技2 天前
第13篇:Linux程序访问控制FPGA端Switch<二>
fpga开发·嵌入式系统·de1-soc开发板