1. Verilog语言基础回顾:模块结构、数据类型、运算符、赋值语句
各位同学好,我是你们的EDA讲师。今天咱们聊聊Verilog最基础的东西——模块结构、数据类型、运算符和赋值语句。别小看这些,我见过太多人在这上面栽跟头了。
1.1 模块结构——Verilog的骨架
Verilog里,模块(module)是最基本的设计单元。说白了,一个模块就是一个黑盒子,有输入、有输出,里面装着逻辑。
模块的基本结构:
module 模块名 (端口列表);
// 端口声明
input [位宽-1:0] 输入端口;
output [位宽-1:0] 输出端口;
inout 双向端口;
// 内部信号声明
wire [位宽-1:0] 内部连线;
reg [位宽-1:0] 内部寄存器;
// 功能描述
// 可以用assign、always块、实例化等
endmodule
我个人习惯把端口声明和内部信号声明分开写,这样代码结构清晰。嗯,这里要注意:端口列表里的名字要和声明里的名字一致,不然编译会报错。我曾经有一次就是因为端口名写错,查了整整一个下午。
1.2 数据类型——线网与寄存器
Verilog的数据类型,核心就两类:wire 和 reg。你想想看,这就像现实中的导线和存储单元。
| 类型 | 默认值 | 驱动方式 | 典型用途 |
|---|---|---|---|
| wire | 高阻态 (z) | 连续赋值 (assign) | 模块间连线、组合逻辑输出 |
| reg | 不定态 (x) | 过程赋值 (always块内) | 寄存器、状态机、时序逻辑 |
小技巧: 我建议初学者记住一个原则——在always块里被赋值的变量,必须声明为reg类型。但别误会,reg不一定会被综合成寄存器,它只是语法上的要求。
除了wire和reg,还有integer(整数)、real(实数)、time(时间)等类型。不过说实话,在RTL设计里,wire和reg占了95%以上的使用场景。
1.3 运算符——数字电路的数学语言
Verilog的运算符,说白了就是数字电路的数学表达。我把它分成几类:
1.3.1 算术运算符
+加法、-减法、*乘法、/除法、%取模**幂运算(Verilog-2001支持)
注意:除法和取模在综合时很耗资源。我在项目中遇到过,用除法器直接导致面积超标。后来改成移位和查表,才把问题解决。
1.3.2 逻辑运算符
&&逻辑与、||逻辑或、!逻辑非- 结果只有0或1
1.3.3 位运算符
&按位与、|按位或、^按位异或、~按位取反~^或^~同或
避坑指南: 我曾经把 && 和 & 搞混过。前者是逻辑与,只返回0或1;后者是按位与,返回逐位运算的结果。比如 4'b1010 && 4'b1100 结果是1,而 4'b1010 & 4'b1100 结果是 4'b1000。完全不一样!
1.3.4 关系运算符
>、<、>=、<=、==、!====、!==(全等比较,会比较x和z状态)
全等比较 === 在仿真调试时特别有用。比如你想检查某个信号是不是高阻态,用 == 会返回x,用 === 才能准确判断。
1.3.5 移位运算符
<<左移、>>右移<<<算术左移、>>>算术右移(保留符号位)
1.3.6 拼接与复制运算符
{a, b}拼接:把多个信号拼在一起{n{a}}复制:把信号a重复n次
举个例子:{4{1'b1}} 等于 4'b1111。这个在初始化寄存器时特别好用。
1.4 赋值语句——连续赋值与过程赋值
赋值语句是Verilog的核心,分两大类:连续赋值 和 过程赋值。
1.4.1 连续赋值 (assign)
用 assign 关键字,用于组合逻辑。等号左边必须是wire类型。
assign sum = a + b;
assign sel = (cnt > 10) ? 1'b1 : 1'b0;
连续赋值是持续驱动的。只要右边表达式变化,左边立即更新。这就像一根导线,输入变了,输出马上跟着变。
1.4.2 过程赋值 (always块内)
过程赋值用在 always 块里,等号左边必须是reg类型。
过程赋值又分两种:
| 类型 | 符号 | 特点 | 典型场景 |
|---|---|---|---|
| 阻塞赋值 | = | 顺序执行,立即更新 | 组合逻辑的always块 |
| 非阻塞赋值 | <= | 并行执行,块结束时更新 | 时序逻辑的always块 |
黄金法则:
- 描述组合逻辑时,用
always @(*)+ 阻塞赋值= - 描述时序逻辑时,用
always @(posedge clk)+ 非阻塞赋值<=
为什么会这样?因为非阻塞赋值能避免竞争冒险。我记得刚入行时,在一个计数器里用了阻塞赋值,结果仿真波形一塌糊涂。后来老工程师告诉我:「时序逻辑用非阻塞,这是铁律。」从那以后,我再也没犯过这个错。
1.4.3 赋值语句示例对比
// 正确的时序逻辑写法
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 8'd0;
else if (en)
cnt <= cnt + 1'b1;
end
// 错误的写法(阻塞赋值用于时序逻辑)
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt = 8'd0; // 不推荐!
else if (en)
cnt = cnt + 1'b1; // 可能出问题!
end
我的经验: 写always块时,先想清楚这是组合逻辑还是时序逻辑。如果是时序逻辑,闭着眼睛用 <=。如果是组合逻辑,用 =。别混着用,不然仿真和综合结果可能不一致。
1.5 本章知识体系
下面这张图,是我梳理的本章知识结构。你把它记在脑子里,后面学解析器时就能快速定位知识点。
好了,这一章的内容就这些。模块结构、数据类型、运算符、赋值语句,这四个东西是Verilog的基石。后面我们讲解析器时,会反复用到这些概念。你先把基础打牢,后面学起来就轻松了。