4. Verilog基础语法(上):模块结构、数据类型(wire/reg)、运算符、assign语句、always块
各位同学,欢迎来到第四讲。今天我们要啃的,是Verilog最核心的几块硬骨头。说实话,很多初学者一上来就被always块和reg搞晕了。别急,我当年也是这样过来的。咱们一个一个拆开讲,保证你听完就能上手写代码。
4.1 模块结构——你的第一个数字电路
Verilog里最基本的单元就是模块(module)。你可以把它想象成一个黑盒子,有输入引脚、输出引脚,里面装着你要实现的逻辑。
module counter (
input wire clk, // 时钟
input wire rst_n, // 复位,低有效
output reg [3:0] count // 4位计数器输出
);
// 内部逻辑写在这里
endmodule
看到没?模块以module开头,以endmodule结束。端口列表里声明了信号的方向(input/output)和类型(wire/reg)。
个人习惯:我写模块时,喜欢把端口声明和内部信号分开写。端口用一行一个,注释标清楚功能。这样过一个月回来再看,自己还能看懂。
4.2 数据类型——wire和reg,到底怎么选?
这是新手最容易踩坑的地方。我见过太多人把wire和reg混用,结果仿真对得上,上板就翻车。
4.2.1 wire——连线型
wire说白了就是一根导线。它只能被连续赋值(assign语句)或者模块的输入输出驱动。你不能在always块里给wire赋值。
wire [7:0] data_bus;
assign data_bus = addr & mask; // 正确,连续赋值
4.2.2 reg——寄存器型
reg并不一定代表寄存器!它只是表示一个变量,可以在always块里被赋值。如果always块是时序逻辑(带时钟),综合出来就是寄存器;如果是组合逻辑(不带时钟),综合出来就是连线。
reg [3:0] temp;
always @(*) begin
temp = a + b; // 组合逻辑,综合出来是wire
end
我曾经踩过的坑:有一次写状态机,把状态变量声明成wire,然后在always块里赋值。仿真器报错我还纳闷了半天。记住:always块里赋值的变量,必须是reg类型。这是语法规定,没得商量。
| 特性 | wire | reg |
|---|---|---|
| 默认值 | 高阻态z | 不定态x |
| 赋值方式 | assign连续赋值 | always块内赋值 |
| 综合结果 | 连线 | 寄存器或连线 |
| 端口类型 | input/output/inout | output(需加reg) |
4.3 运算符——数字电路的数学基础
Verilog的运算符跟C语言很像,但有几个地方要特别注意。
4.3.1 算术运算符
+、-、*、/、%。注意:乘除法综合出来很耗资源。我一般只在仿真里用除法,上板的话会用移位或者查找表代替。
4.3.2 位运算符
&、|、^、~。这些是硬件设计的核心,直接对应门电路。
4.3.3 归约运算符
这个很多人会忽略。比如&a表示把a的所有位相与,结果只有1位。我在做数据校验时经常用。
wire [7:0] data;
wire parity;
assign parity = ^data; // 异或归约,生成奇偶校验位
4.3.4 拼接运算符
{}可以把多个信号拼在一起。这个太实用了,我几乎每个项目都会用到。
wire [3:0] high, low;
wire [7:0] combined;
assign combined = {high, low}; // 拼接成8位总线
4.4 assign语句——连续赋值的艺术
assign语句用来描述组合逻辑。它的特点是:只要右边表达式里的信号发生变化,左边就会立刻更新。
assign out = (sel) ? a : b; // 二选一多路器
嗯,这里要注意:assign不能用在always块里,也不能对reg类型赋值。这是很多新手写代码报错的原因。
核心原则:assign描述的是硬件连线,不是软件赋值。你写assign的时候,脑子里要想的是「这根线连到了那个门的输出」,而不是「把值赋给变量」。
4.5 always块——时序逻辑的基石
always块是Verilog里最灵活、也最容易出错的构造。它有两种主要用法:
4.5.1 组合逻辑always块
always @(*) begin
if (sel)
out = a;
else
out = b;
end
敏感列表用@(*),表示所有输入信号变化都会触发。这种写法综合出来就是组合逻辑。
4.5.2 时序逻辑always块
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
count <= 4'b0;
else
count <= count + 1'b1;
end
敏感列表里有时钟沿和复位沿。注意这里用的是非阻塞赋值<=,这是时序逻辑的标准写法。
避坑指南:我曾经在一个项目里,把组合逻辑和时序逻辑混写在同一个always块里。结果仿真没问题,上板后时序一塌糊涂。后来我养成了习惯:组合逻辑用阻塞赋值=,时序逻辑用非阻塞赋值<=,永远不要混用。
4.6 知识体系总览
下面这张图,是我根据多年经验总结的Verilog基础语法脉络。你照着这个框架去学,不会走偏。
这张图把今天讲的内容串起来了。你仔细看:模块是外壳,wire/reg是材料,assign和always是两种不同的「施工方式」。搞懂这些,你就能搭建出基本的数字电路了。
好了,今天的内容就到这里。记住我反复强调的那句话:写Verilog的时候,脑子里想的是硬件,不是软件。下一章我们会继续深入,讲阻塞赋值和非阻塞赋值的区别,那才是真正考验功底的地方。
课后练习:写一个4位加法器,用assign实现组合逻辑,再用always块实现带时钟的累加器。对比两种写法的综合结果。
公众号:蓝海资料掘金营,微信deep3321