2. Verilog基础回顾:模块结构、数据类型、连续赋值与过程赋值、组合逻辑与时序逻辑
各位同学,咱们直接进入正题。这一章我打算带大家快速过一遍Verilog的核心基础。别觉得基础就简单,很多坑恰恰是基础不牢导致的。我自己带项目时,发现不少老手也会在连续赋值和过程赋值上翻车。咱们今天就把这些掰扯清楚。
2.1 模块结构——Verilog的骨架
Verilog里,模块(module)是最基本的设计单元。说白了,一个模块就是一个黑盒子,有输入、有输出,内部实现具体逻辑。
一个典型的模块长这样:
module counter (
input wire clk, // 时钟
input wire rst_n, // 异步复位,低有效
input wire en, // 使能
output reg [3:0] count // 4位计数器输出
);
// 时序逻辑:计数器核心
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
count <= 4'd0;
else if (en)
count <= count + 1'b1;
end
endmodule
嗯,这里要注意几点:
- 端口声明:input/output/inout,每个端口都要明确方向。
- 数据类型:wire和reg,后面会细讲。
- 逻辑块:always块、assign语句,是模块的“血肉”。
我个人习惯,写模块时先把端口列清楚,再写内部逻辑。这样结构清晰,后期维护也方便。我曾经接手过一个项目,模块端口乱成一锅粥,光理清接口就花了两天……
2.2 数据类型——wire和reg,别搞混了
很多初学者会问:wire和reg到底啥区别?
简单说:
- wire:线网类型,用于连续赋值(assign语句)或模块端口连接。它不存储值,只是“导线”。
- reg:寄存器类型,用于过程赋值(always块内)。它可以在时钟沿或电平触发时存储值。
但注意!reg不一定综合成寄存器。你想想看,在组合逻辑的always块里,reg只是用来描述逻辑,综合出来还是组合电路。
举个例子:
// 组合逻辑:reg综合成组合电路
reg [3:0] sum;
always @(*) begin
sum = a + b;
end
// 时序逻辑:reg综合成触发器
reg [3:0] count;
always @(posedge clk) begin
count <= count + 1;
end
我在项目中遇到过有人把组合逻辑的reg误当成寄存器,结果仿真和综合对不上,查了半天。记住:综合工具看的是赋值方式,不是数据类型。
2.3 连续赋值 vs 过程赋值——两种思维模式
这是Verilog里最容易混淆的地方。咱们分清楚:
| 特性 | 连续赋值(assign) | 过程赋值(always块内) |
|---|---|---|
| 使用场景 | 组合逻辑、数据流描述 | 组合逻辑、时序逻辑 |
| 赋值对象 | 必须是wire类型 | 必须是reg类型 |
| 执行方式 | 输入变化立即更新 | 敏感列表触发时执行 |
| 典型写法 | assign y = a & b; |
always @(*) y = a & b; |
连续赋值,说白了就是“硬件连线”。输入一变,输出立刻跟着变。适合描述简单的组合逻辑,比如加法器、多路选择器。
过程赋值,则是在always块里赋值。它有两种:
- 阻塞赋值(=):顺序执行,适合组合逻辑。
- 非阻塞赋值(<=):并行执行,适合时序逻辑。
我曾经犯过一个低级错误:在时序逻辑里用了阻塞赋值,结果仿真波形完全不对。后来养成习惯:组合逻辑用=,时序逻辑用<=,再也没出过问题。
2.4 组合逻辑 vs 时序逻辑——两种电路形态
这两个概念是数字设计的基石。咱们用一张图来理解:
组合逻辑:输出只由当前输入决定。比如与门、或门、加法器。没有记忆,没有时钟。
时序逻辑:输出不仅看当前输入,还要看之前的状态。比如计数器、状态机。它依赖时钟沿或电平触发。
在Verilog里,区分它们很简单:
- 组合逻辑:用
always @(*)或assign,赋值用=。 - 时序逻辑:用
always @(posedge clk),赋值用<=。
@(*),工具会自动推导所有输入信号。别手动列,容易漏。
我记得有一次,一个同事写组合逻辑时手动列敏感列表,漏了一个信号,仿真结果死活不对。后来改成@(*),问题立刻解决。嗯,这种坑踩过一次就记住了。
2.5 实战建议——从基础开始养成好习惯
说了这么多,总结几条我自己的经验:
- 模块命名要规范:比如
uart_tx、spi_master,一看就知道干啥的。 - 端口顺序要统一:我习惯先输入后输出,时钟复位放最前面。
- 组合时序分开写:别把组合逻辑和时序逻辑混在一个always块里,后期维护会疯掉。
- 仿真验证不能省:写一个模块,至少跑个简单的testbench,确认功能正确。
我曾经因为偷懒没仿真,直接上板调试,结果一个简单的计数器逻辑错了,浪费了整整一天。从那以后,我坚持“先仿真,后上板”。
好了,这一章的基础内容就到这里。记住:Verilog基础不牢,后面写复杂逻辑时会很痛苦。多写、多练、多仿真,慢慢就上手了。
公众号:蓝海资料掘金营,微信deep3321