3. Verilog基础(一):模块与端口、数据类型(wire、reg)、连续赋值(assign)、运算符
各位同学,今天咱们正式进入Verilog的世界。说实话,每次带新人,我最怕的就是他们一上来就写代码,结果连最基本的模块结构都搞不清楚。别急,咱们一步一步来。
3.1 模块与端口——Verilog的“黑盒子”
Verilog里,模块(module)就是最基本的设计单元。你可以把它想象成一个黑盒子——外面的人只看到它的引脚(端口),里面怎么实现的,那是你的事。
一个模块长这样:
module counter (
input wire clk, // 时钟
input wire rst_n, // 复位,低有效
output reg [3:0] count // 4位计数器输出
);
// 内部逻辑写在这里
endmodule
嗯,这里要注意:module和endmodule是成对出现的,少了哪个都不行。我在项目中见过有人把endmodule拼成endmodule——少了个字母,编译报错找了半天。
端口方向
端口有三种方向:
| 方向 | 关键字 | 说明 |
|---|---|---|
| 输入 | input |
信号从外部流入模块内部 |
| 输出 | output |
信号从模块内部流出到外部 |
| 双向 | inout |
既能输入也能输出(慎用) |
注意:inout端口在实际项目中尽量少用。我吃过亏——双向总线没处理好驱动冲突,仿真没问题,上板就冒烟。后来老老实实改成单向端口加三态控制。
3.2 数据类型:wire vs reg
很多初学者搞不清wire和reg的区别。说白了:
- wire——代表一根导线,它本身不存储值,值由驱动源决定。
- reg——代表一个寄存器,它能存储值,在always块里被赋值。
举个例子:
wire a, b; // 两根导线
wire sum; // 导线,用于连接加法结果
assign sum = a ^ b; // 连续赋值,sum的值由a和b实时决定
reg [7:0] data_reg; // 8位寄存器
always @(posedge clk) begin
data_reg <= data_in; // 时钟上升沿才更新
end
我个人习惯:组合逻辑用wire,时序逻辑用reg。但注意,在always块里赋值的变量必须是reg类型——哪怕它综合出来只是一根导线。
小技巧:如果你不确定用wire还是reg,先问自己:这个信号是在always块里被赋值吗?是→用reg;否→用wire。
3.3 连续赋值(assign)
assign是Verilog里最直接的赋值方式。它描述的是组合逻辑——只要右边信号变化,左边立刻更新。
assign y = a & b; // 与门
assign z = ~(a | b); // 或非门
我曾经遇到一个bug:有人用assign给reg类型赋值,编译报错。记住,assign的左边必须是wire类型(或者wire的向量)。
连续赋值可以同时写多个:
assign {carry, sum} = a + b + cin; // 全加器,carry是进位
嗯,这里要注意:assign是并行执行的,没有先后顺序。你写10条assign,它们同时生效。
3.4 运算符
Verilog的运算符和C语言很像,但有些细节不一样。我整理了一张表:
| 类别 | 运算符 | 说明 |
|---|---|---|
| 算术 | + - * / % |
加减乘除取模。注意:除法综合出来很费资源 |
| 位运算 | & | ~ ^ ^~ |
与、或、非、异或、同或 |
| 逻辑 | && || ! |
逻辑与、或、非,结果只有0或1 |
| 关系 | > < >= <= == != |
比较运算符,结果也是0或1 |
| 移位 | << >> |
左移、右移,空位补0 |
| 拼接 | { } |
把多个信号拼成一个向量 |
| 条件 | ? : |
三目运算符,类似C语言 |
举个例子,看看实际怎么用:
wire [3:0] a, b;
wire [4:0] sum;
wire eq;
assign sum = a + b; // 算术运算,注意进位
assign eq = (a == b); // 关系运算
assign result = {a[3:0], b[3:0]}; // 拼接成8位
重点:位运算和逻辑运算的区别。位运算是对每一位操作,逻辑运算只关心整体是0还是非0。
比如:
而
比如:
4'b1010 & 4'b1100 结果是 4'b1000(位与)而
4'b1010 && 4'b1100 结果是 1'b1(逻辑与,因为两个都不为0)
3.5 知识体系图
下面这张图帮你理清本章的知识脉络:
3.6 避坑指南
最后,分享几个我踩过的坑:
- 端口类型不匹配——我曾经把一个output reg连到另一个模块的input wire上,结果仿真一直X态。检查了半天,发现是位宽不匹配。
- assign左边用了reg——编译报错,新手常犯。记住,assign左边只能是wire。
- 运算符优先级搞混——比如
a & b | c,到底是(a & b) | c还是a & (b | c)?我建议:多用括号,别省那几下键盘。
我的习惯:写代码时,所有运算符都加括号。虽然看起来啰嗦,但可读性大大提升,而且不会因为优先级问题出bug。
好了,这一章的内容就到这里。记住,Verilog不是C语言,它是描述硬件的。多写、多仿真、多思考,慢慢就有感觉了。
公众号:蓝海资料掘金营,微信deep3321