4. Verilog基础(一):模块结构、数据类型、运算符、assign语句
各位同学,咱们今天开始正式进入Verilog的世界。说实话,很多初学者一上来就被各种语法吓住了,觉得这玩意儿跟C语言差不多,结果写出来的代码综合出来一堆莫名其妙的电路。我当年也踩过这个坑,所以今天咱们慢慢来,先把最核心的几个概念掰扯清楚。
4.1 模块结构——Verilog的“黑盒子”
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'b0000;
else
count <= count + 1'b1;
end
endmodule
注意看,module和endmodule就像一对括号,把整个设计包起来。端口列表里声明了信号的方向(input/output/inout)和类型(wire/reg)。
rst_n命名,后面的_n表示低电平有效,这是行业惯例。
4.2 数据类型——wire和reg的区别
这是新手最容易搞混的地方。我直接说结论:
- wire:连线型,用于组合逻辑。它本身不存储值,只是把两个点连起来。
- reg:寄存器型,用于时序逻辑。它可以在时钟沿到来时保持住值。
但注意!reg并不一定真的综合成寄存器。在always块里赋值的变量,必须声明为reg,哪怕它只是组合逻辑。这是语法规定,别问为什么,记住就行。
举个例子:
wire a, b; // 组合逻辑连线
wire sum; // 组合逻辑输出
reg q; // 时序逻辑寄存器
assign sum = a ^ b; // 用assign驱动wire
always @(posedge clk) begin
q <= sum; // 在时钟上升沿,把sum的值锁存到q
end
always块里给wire类型赋值,编译器直接报错。后来养成了习惯:只要是在always或initial里赋值的变量,一律用reg;用assign驱动的,一律用wire。
4.3 运算符——跟C语言很像,但有坑
Verilog的运算符大部分跟C语言一样,但有几个特别容易出问题的地方,我重点说一下。
| 运算符类别 | 符号 | 说明 |
|---|---|---|
| 算术运算符 | + - * / % | 注意:除法综合出来很费资源,尽量少用 |
| 关系运算符 | > < >= <= == != | 结果返回1或0 |
| 逻辑运算符 | && || ! | 用于条件判断,结果也是1或0 |
| 位运算符 | & | ~ ^ | 按位操作,注意跟逻辑运算符区分 |
| 拼接运算符 | { } | 把多个信号拼成一个宽信号 |
| 缩减运算符 | & | ^ | 对向量的所有位做与、或、异或 |
这里有个经典陷阱:逻辑运算符和位运算符的区别。比如:
wire [3:0] a = 4'b1010;
wire [3:0] b = 4'b1100;
wire logic_result = a && b; // 逻辑与:a非零且b非零,结果为1
wire bit_result = a & b; // 位与:按位操作,结果为4'b1000
你看,&&把整个向量当成一个布尔值,而&是对每一位分别操作。我见过有人把&&写成&,结果仿真波形完全不对,查了半天才发现是这里的问题。
if (a && b),一定要用逻辑运算符。写数据通路时,比如assign c = a & b,用位运算符。这两个搞混了,综合出来的电路会完全不一样。
4.4 assign语句——连续赋值
assign是Verilog里最常用的语句之一,它用来描述组合逻辑。说白了,就是输入一变,输出立刻跟着变,没有延迟(当然,实际电路有门延迟,但仿真里是立即变化的)。
基本语法:
assign 信号名 = 表达式;
举个例子,实现一个半加器:
module half_adder (
input wire a, b,
output wire sum, carry
);
assign sum = a ^ b; // 异或,得到和
assign carry = a & b; // 与,得到进位
endmodule
注意,assign的左边必须是wire类型,不能是reg。而且assign语句是并行执行的,跟书写顺序无关。你想想看,硬件电路本来就是同时工作的,所以代码里写100条assign,它们也是同时赋值。
assign就用assign,少用always @(*)。因为assign更直观,综合工具优化起来也更高效。只有在需要if-else或case这种复杂逻辑时,才考虑用always块。
4.5 知识体系总览
下面这张图,把今天讲的内容串起来了。你仔细看看,模块是外壳,里面装着数据类型、运算符和赋值语句,这三者共同构成了Verilog的基础。
嗯,今天的内容就到这里。模块结构、数据类型、运算符、assign语句,这四个东西是Verilog的基石。你把这几个搞明白了,后面学时序逻辑、状态机什么的,就会轻松很多。
记住,写Verilog的时候,脑子里要时刻想着硬件电路,而不是软件程序。你写的每一行代码,最终都会变成实实在在的门电路。这个思维转变过来了,你就入门了。