2. Verilog HDL基础(一):模块结构、数据类型(wire/reg)、连续赋值(assign)、运算符
各位同学,今天我们来聊聊Verilog HDL最基础的东西。说实话,很多新手一上来就急着写代码,结果连模块长什么样都没搞清楚。我当年带过一个实习生,写了三天代码,最后发现连端口方向都写反了……嗯,咱们先把地基打牢。
2.1 模块结构——Verilog的“黑盒子”
Verilog里最基本的单元就是模块(module)。你可以把它想象成一个黑盒子:外面能看到的是端口(输入输出),里面装的是具体逻辑。
一个完整的模块长这样:
module counter (
input clk, // 时钟
input rst_n, // 复位(低有效)
input en, // 使能
output reg [3:0] cnt // 4位计数器输出
);
// 内部逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 4'd0;
else if (en)
cnt <= cnt + 1'b1;
end
endmodule
注意几个关键点:
- 端口声明:input/output/inout 要写清楚。我见过有人把input写成output,仿真半天查不出错。
- 模块名:最好有意义,别用module1、module2这种。
- endmodule:别忘了这个结尾。少写一个字母,编译就报错。
2.2 数据类型——wire和reg
Verilog里最常用的两种数据类型就是wire和reg。很多初学者搞不清它们的区别,其实很简单:
| 类型 | 含义 | 赋值方式 | 典型用途 |
|---|---|---|---|
| wire | 线网型 | 连续赋值(assign) | 组合逻辑、模块间连线 |
| reg | 寄存器型 | 过程赋值(always块内) | 时序逻辑、存储状态 |
wire说白了就是一根导线。你给它赋值,它就保持那个值。比如:
wire a, b, c;
assign c = a & b; // c的值由a和b实时决定
reg则像一个存储单元。它会在时钟沿到来时更新值。比如:
reg [7:0] data_reg;
always @(posedge clk) begin
data_reg <= data_in; // 时钟上升沿才更新
end
2.3 连续赋值——assign语句
assign是Verilog里最直接的赋值方式。它的特点是:右边一变,左边立刻跟着变。就像电线一样,没有延迟。
基本语法:
assign 左边信号 = 右边表达式;
举个例子:
wire [3:0] sum;
wire [3:0] a, b;
assign sum = a + b; // 加法器,a和b变化时sum立即变化
assign只能用于wire类型,不能用于reg。这个规则很死,别想着打破它。
wire [7:0] data_out;
assign data_out = {data_high, data_low}; // 拼接
这样写比用always块简洁多了。
2.4 运算符——Verilog的“计算器”
Verilog的运算符和C语言很像,但有些细节不一样。我按类别给大家梳理一下:
2.4.1 算术运算符
+加法-减法*乘法(综合会消耗大量资源)/除法(尽量别用,综合出来面积巨大)%取模(同样慎用)
注意:加减法没问题,乘除法要小心。我做过一个项目,用了除法器,结果面积翻了三倍……后来改成移位实现了。
2.4.2 逻辑运算符
&&逻辑与||逻辑或!逻辑非
逻辑运算符返回的是1位布尔值(0或1)。
2.4.3 位运算符
&按位与|按位或^按位异或~按位取反
位运算符是对每一位独立操作。比如 4'b1010 & 4'b1100 = 4'b1000。
2.4.4 关系运算符
>大于<小于>=大于等于<=小于等于==等于!=不等于
关系运算符返回1位布尔值。注意:===和!==是case相等比较,会考虑x和z状态,仿真时有用,但综合不支持。
2.4.5 移位运算符
<<左移>>右移
移位运算符在设计中非常常用。比如乘以2可以用左移1位实现:a << 1。
2.4.6 拼接运算符
{} 可以把多个信号拼在一起:
wire [7:0] byte_data;
wire [3:0] high, low;
assign byte_data = {high, low}; // 高4位和低4位拼接
2.5 知识体系总览
下面这张图把本章的核心知识点串起来了,你可以对照着看:
好了,今天的内容就到这里。记住:模块结构是骨架,wire和reg是血肉,assign是连接线,运算符是逻辑核心。把这几个概念吃透,后面写代码就会顺手很多。