1. Verilog基础与数字设计入门
各位同学好,我是你们的讲师。今天咱们聊聊Verilog——这个在数字芯片设计领域摸爬滚打了几十年的硬件描述语言。说实话,我刚开始接触Verilog时也觉得它有点“怪”,跟C语言长得像,但思维方式完全不同。不过别担心,这一章我会把最核心的东西掰开揉碎了讲给你听。
1.1 Verilog的前世今生
Verilog诞生于1984年,由Gateway Design Automation公司开发。那时候数字电路设计还在用手工画门级电路图,效率低得吓人。Verilog的出现,说白了就是让设计师能用“写代码”的方式描述硬件。
我记得2005年刚入行时,老工程师还在用原理图设计,我试着用Verilog写了个简单的计数器,他们看了直摇头。结果后来流片验证,我的设计一次通过,他们才服气。嗯,这就是技术进步的力量。
Verilog在1995年成为IEEE标准(IEEE 1364),2005年又吸收了SystemVerilog的很多特性。现在,它和VHDL并列为两大主流硬件描述语言。我个人习惯用Verilog,因为它的语法更简洁,上手快。
1.2 模块结构——Verilog的骨架
Verilog设计的基本单元是模块(module)。一个模块就像一块芯片,有输入输出引脚,内部有逻辑功能。来看个最简单的例子:
module and_gate (
input wire a, // 输入端口a
input wire b, // 输入端口b
output wire y // 输出端口y
);
assign y = a & b; // 与门逻辑
endmodule
这个模块描述了一个两输入与门。注意看:input和output定义了端口方向,wire表示连线类型。模块以endmodule结束,就像C语言的大括号。
1.3 数据类型——wire和reg
Verilog中最常用的两种数据类型是wire和reg。很多新手搞不清它们的区别,我当年也迷糊过。
| 类型 | 含义 | 典型用法 |
|---|---|---|
| wire | 连线型,表示物理连线 | assign语句、模块端口连接 |
| reg | 寄存器型,表示存储单元 | always块中的赋值 |
简单来说:wire就像一根导线,只能被连续赋值驱动;reg就像一个小盒子,可以在时钟边沿保存数据。但注意,reg不一定会综合成寄存器——如果你在组合逻辑的always块里用reg,它综合出来还是连线。
1.4 操作符——数字电路的“数学工具”
Verilog的操作符跟C语言很像,但有些特殊的地方。我整理了一张表:
| 类别 | 操作符 | 说明 |
|---|---|---|
| 算术 | + - * / % | 加减乘除取模 |
| 位运算 | & | ~ ^ | 与、或、非、异或 |
| 逻辑 | && || ! | 逻辑与、或、非 |
| 关系 | > < >= <= == != | 比较运算 |
| 移位 | << >> | 左移、右移 |
| 拼接 | { } | 将多个信号拼接成更宽的向量 |
这里有个容易混淆的点:位运算和逻辑运算的区别。位运算是按位操作的,逻辑运算只返回0或1。举个例子:4'b1010 & 4'b1100结果是4'b1000;而4'b1010 && 4'b1100结果是1'b1(因为两个数都不为0)。
拼接操作符{}是我个人非常喜欢用的。比如你想把两个4位信号拼成一个8位信号:wire [7:0] result = {a, b};。简洁又直观。
1.5 组合逻辑建模——assign和always
组合逻辑就是输出只取决于当前输入,跟历史状态无关。Verilog里建模组合逻辑有两种方式:assign和always。
1.5.1 使用assign连续赋值
assign是最直接的方式,它描述了一个持续不断的赋值关系。比如:
module full_adder (
input wire a, b, cin,
output wire sum, cout
);
assign sum = a ^ b ^ cin; // 和
assign cout = (a & b) | (a & cin) | (b & cin); // 进位
endmodule
这个全加器用了三个异或和几个与或门。注意assign左边必须是wire类型,右边可以是表达式。综合工具会自动生成对应的门级电路。
1.5.2 使用always块建模
always块更灵活,可以描述更复杂的逻辑。对于组合逻辑,要用敏感列表列出所有输入信号:
module mux_2to1 (
input wire [3:0] a, b,
input wire sel,
output reg [3:0] y
);
always @(*) begin
if (sel)
y = b;
else
y = a;
end
endmodule
这里always @(*)表示对所有输入信号敏感——只要a、b、sel中任何一个变化,就重新计算y。注意输出y声明为reg类型,因为它在always块里被赋值。
1.6 知识体系总览
为了让你更直观地理解本章的知识结构,我画了一张图:
这张图展示了本章的五个核心知识点,从历史背景到具体建模方式,层层递进。你学完这一章,应该能自己写出简单的组合逻辑模块了。
1.7 本章小结
好了,咱们来捋一捋今天讲了什么:
- Verilog历史:从1984年诞生到成为IEEE标准,它让数字设计从画图变成了写代码。
- 模块结构:module是基本单元,端口声明要规范。
- 数据类型:wire是连线,reg是寄存器(但组合逻辑里用reg不一定会综合出寄存器)。
- 操作符:跟C语言类似,但要注意位运算和逻辑运算的区别。
- 组合逻辑建模:assign适合简单逻辑,always适合复杂逻辑,但一定要避免生成latch。
说实话,这些基础内容看起来简单,但我在实际项目中见过太多人栽在数据类型和latch问题上。你想想看,如果连wire和reg都分不清,后面写状态机、写时序逻辑时肯定会出问题。所以这一章一定要吃透。
下一章咱们会深入时序逻辑,讲触发器、寄存器、计数器这些更“硬核”的东西。到时候你会发现,有了今天的基础,学起来会轻松很多。
公众号:蓝海资料掘金营,微信deep3321