2. Verilog语法精讲(上):模块结构、数据类型、运算符、连续赋值
各位同学,咱们今天聊聊Verilog最基础的东西。说实话,很多新手一上来就急着写代码,结果连模块长什么样都没搞明白。我当年带过一个实习生,写了三天代码,最后发现连端口方向都写反了——这种坑,咱们今天一次性填平。
2.1 模块结构:芯片设计的“乐高积木”
Verilog里,模块(module)就是最基本的构建单元。你可以把它想象成一块乐高积木——有接口(端口),有内部逻辑(功能),还能跟其他积木拼在一起。
一个标准的模块长这样:
module counter (
input wire clk, // 时钟
input wire rst_n, // 复位(低有效)
output reg [3:0] count // 4位计数器输出
);
// 内部逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
count <= 4'b0;
else
count <= count + 1'b1;
end
endmodule
注意几个关键点:
- 端口声明:input、output、inout,方向不能搞错。我见过有人把input写成output,仿真能过,综合直接报错。
- 数据类型:wire和reg,后面细讲。
- endmodule:模块结束标志,千万别漏了。漏了这个,综合工具会给你一堆莫名其妙的错误。
2.2 数据类型:wire vs reg,到底怎么选?
这个问题,几乎每个初学者都会问。我当年也纠结过很久。说白了:
- wire:连线型,用于组合逻辑。比如assign语句的输出、模块端口之间的连接。
- reg:寄存器型,用于时序逻辑。比如always块里赋值的变量。
但有个坑:reg不一定真的综合成寄存器。如果你在always块里写组合逻辑(比如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'b1;
end
2.3 运算符:别小看这些符号
Verilog的运算符跟C语言很像,但有几个地方特别容易出错。
| 类别 | 运算符 | 说明 |
|---|---|---|
| 算术 | + - * / % | 注意:乘法综合出来面积大,除法尽量少用 |
| 逻辑 | && || ! | 结果是1位(true/false) |
| 按位 | & | ~ ^ | 逐位操作,结果宽度跟操作数相同 |
| 归约 | & | ~& ~| ^ ~^ | 单目运算符,把多位宽向量归约为1位 |
| 移位 | << >> | 左移右移,空位补0 |
| 拼接 | { } | 把多个信号拼在一起,非常实用 |
你想想看,归约运算符是不是很眼熟?其实就是把多位宽信号的所有位做与、或、异或。我调试时经常用归约或(|)来检查一个总线里有没有任何一位为1——比写循环快多了。
2.4 连续赋值(assign):组合逻辑的“直通车”
连续赋值,说白了就是用assign语句把组合逻辑“连”到wire上。它的特点是:只要右边表达式变了,左边立刻更新。
基本语法:
assign wire_name = expression;
举个例子:
module adder (
input wire [3:0] a, b,
output wire [4:0] sum
);
assign sum = a + b;
endmodule
这里sum是wire类型,a和b变了,sum马上跟着变。这就是组合逻辑的本质——没有时钟,没有存储,纯靠连线。
连续赋值有几个要点:
- 左边必须是wire(或者wire类型的端口)。不能是reg。
- 右边可以是任何表达式:运算符、函数调用、条件表达式(? :)都可以。
- 多条assign语句并行执行,跟顺序无关。这个跟软件编程完全不同。
对了,条件运算符(? :)在assign里特别常用。比如:
assign max = (a > b) ? a : b;
这比写always块简洁多了。你想想看,是不是这个理?
2.5 知识体系总览
下面这张图,把本章的核心逻辑串起来了。建议你多看几遍,理解各个概念之间的关系。
好了,这一章的内容就这些。模块结构、数据类型、运算符、连续赋值——这四个东西是Verilog的基石。你把这些搞明白了,后面学时序逻辑、状态机什么的,就会轻松很多。
可可精品资料库