3. Verilog基础回顾:模块结构、数据类型、连续赋值与过程赋值、阻塞与非阻塞赋值
各位同学,咱们今天聊点实在的。Verilog 这东西,说白了就是硬件描述语言,不是软件编程。我见过太多人把 Verilog 当 C 语言写,结果仿真跑得欢,综合出来一堆问题。嗯,咱们今天就把这些基础概念掰扯清楚。
3.1 模块结构——Verilog 的基本单元
模块(module)是 Verilog 的骨架。每个设计,不管多复杂,最终都是由一个个模块拼起来的。我个人习惯把模块比作一个黑盒子——有输入、有输出、内部有逻辑。
module counter (
input wire clk,
input wire rst_n,
output reg [3:0] count
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
count <= 4'd0;
else
count <= count + 1'b1;
end
endmodule
你看,模块的端口声明、内部逻辑,一目了然。我在项目中遇到过有人把端口方向写反了,仿真没问题,但综合后死活连不上。所以,写模块时端口方向一定要仔细核对。
uart_tx,别用驼峰命名。这是行业惯例,方便脚本处理。
3.2 数据类型——wire 和 reg 的区别
很多新手搞不清 wire 和 reg。其实很简单:
- wire:连线类型,用于组合逻辑。说白了就是物理连线的抽象,不能存储值。
- reg:寄存器类型,用于时序逻辑。它可以保存值,直到被重新赋值。
但注意,这不是绝对的。在 always 块里赋值的变量,必须声明为 reg;在 assign 语句里赋值的,必须用 wire。我刚开始学的时候也犯过混,后来记住一句话:always 块里用 reg,assign 用 wire,基本不会错。
| 类型 | 默认值 | 使用场景 | 能否存储 |
|---|---|---|---|
| wire | 高阻态 z | assign、模块端口连接 | 否 |
| reg | 不定态 x | always 块内赋值 | 是 |
logic 类型可以替代 wire 和 reg,但老项目里还是 wire/reg 居多。我建议新手先掌握传统写法,再学 SV 的简化。
3.3 连续赋值与过程赋值
连续赋值用 assign,过程赋值用 always 块。这是两种完全不同的赋值方式。
连续赋值(assign)
assign 语句是组合逻辑。只要右边信号变化,左边立即更新。你想想看,这就像一根导线,输入变了输出马上变。
assign sum = a + b;
assign sel = (addr > 8'h7F) ? 1'b1 : 1'b0;
我曾经在项目中用 assign 写了一个复杂的多级组合逻辑,结果时序跑不过。后来拆成两级流水线才解决。所以,assign 虽然方便,但别滥用,组合逻辑太深会出问题。
过程赋值(always)
always 块可以描述组合逻辑,也可以描述时序逻辑。区别在于敏感列表:
- 组合逻辑:敏感列表用
@(*)或列出所有输入信号 - 时序逻辑:敏感列表用
@(posedge clk)或@(negedge clk)
// 组合逻辑
always @(*) begin
if (sel)
out = a;
else
out = b;
end
// 时序逻辑
always @(posedge clk) begin
if (rst_n)
q <= d;
else
q <= 1'b0;
end
3.4 阻塞赋值与非阻塞赋值——面试必考题
这个知识点,我面试过的人十个有八个答不全。阻塞赋值用 =,非阻塞赋值用 <=。区别在哪?
- 阻塞赋值(=):顺序执行,上一条没执行完,下一条等着。适合组合逻辑。
- 非阻塞赋值(<=):并行执行,所有赋值同时计算,然后同时更新。适合时序逻辑。
为什么会这样?因为硬件是并行的。你想想看,一个寄存器在时钟沿同时采样多个输入,如果用了阻塞赋值,就会出现竞争——谁先赋值谁后赋值,结果不确定。
我曾经在项目中遇到一个 bug:一个 always 块里同时用阻塞和非阻塞赋值,结果仿真和综合结果不一致。查了两天才发现是赋值方式混用了。从那以后,我给自己定了个规矩:
- 组合逻辑 always 块:用阻塞赋值
= - 时序逻辑 always 块:用非阻塞赋值
<= - 同一个 always 块里,不要混用两种赋值方式
// 正确示例:时序逻辑用非阻塞
always @(posedge clk) begin
q1 <= d;
q2 <= q1;
end
// 错误示例:时序逻辑用阻塞
always @(posedge clk) begin
q1 = d;
q2 = q1; // 这里 q2 得到的是 d,不是 q1 的旧值
end
你看,上面那个错误示例,用阻塞赋值时 q2 直接得到了 d 的值,而不是 q1 的旧值。这在移位寄存器里就完全错了。
3.5 知识体系总览
为了让大家更直观地理解本章内容,我画了一张图。这张图把 Verilog 基础的核心概念串起来了:
这张图把模块结构、数据类型、赋值方式串在了一起。你从上往下看,左边是模块的骨架,中间是数据类型的选择,右边是赋值方式的区分。最下面就是阻塞和非阻塞这个核心考点。
好了,今天的内容就到这里。记住一句话:写 Verilog 的时候,时刻问自己——这是组合逻辑还是时序逻辑?该用 assign 还是 always?该用 = 还是 <=? 想清楚了再下笔,能省掉后面很多 debug 的时间。
- 模块是基本单元,端口方向要写对
- wire 用于 assign,reg 用于 always
- 连续赋值是组合逻辑,过程赋值可组合可时序
- 时序逻辑用非阻塞赋值,组合逻辑用阻塞赋值
- 同一个 always 块里不要混用两种赋值
可可精品资料库