3、硬件描述语言基础:Verilog与VHDL对比、模块化设计思想、并行与顺序语句的理解
好,咱们今天聊聊硬件描述语言。很多刚入行的朋友问我,Verilog和VHDL到底选哪个?我的回答是——别纠结,两个都得会看,但入门我推荐Verilog。
为什么?说白了,Verilog的语法更像C,上手快。我当年在学校里先学的VHDL,那叫一个痛苦。后来工作中接触Verilog,三天就能写模块了。但这不是说VHDL不好,它严谨,适合大型项目。
3.1 Verilog与VHDL:一场没有输家的对比
先看个直观的对比表:
| 对比项 | Verilog | VHDL |
|---|---|---|
| 语法风格 | 类C,简洁 | 类Ada,严谨 |
| 数据类型 | reg、wire,相对简单 | std_logic、integer等,类型丰富 |
| 设计粒度 | 适合RTL级描述 | 适合系统级描述 |
| 学习曲线 | 平缓,易上手 | 陡峭,但逻辑清晰 |
| 行业应用 | ASIC/FPGA主流 | 军工、航天领域常见 |
我个人习惯是:写模块用Verilog,看文档用VHDL。你想想看,一个项目组里,可能两种语言混着用。我遇到过最夸张的一次,一个IP核用Verilog写,测试bench用VHDL写,仿真照样跑得飞起。
核心观点:语言只是工具,思想才是灵魂。不管用哪种语言,你描述的都是硬件电路,不是软件程序。
3.2 模块化设计思想:把大问题拆成小积木
模块化设计,说白了就是“分而治之”。一个复杂的FPGA设计,如果全写在一个文件里,那调试起来简直是噩梦。
我一般这样划分模块:
- 顶层模块:只做连线,不写逻辑。就像PCB上的插座,只管把各个子模块插上去。
- 功能模块:每个模块只做一件事。比如一个UART模块,只负责串口收发。
- 接口清晰:模块之间的信号越少越好。我见过有人一个模块引出50个端口,那还不如不拆。
举个例子,一个简单的计数器模块:
// Verilog 计数器模块
module counter (
input wire clk,
input wire rst_n,
input wire en,
output reg [7:0] count
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
count <= 8'd0;
else if (en)
count <= count + 1'b1;
end
endmodule
你看,这个模块只做计数。如果哪天要改成减法计数,我只需要改这一处,其他模块完全不受影响。这就是模块化的好处。
小技巧:写模块时,先画接口图,再写代码。我习惯用Excel列个端口清单,标清楚方向、位宽、功能。这样写代码时思路特别清晰。
3.3 并行与顺序语句:硬件思维的分水岭
这是很多新手栽跟头的地方。软件里,代码是一行一行执行的。硬件里呢?所有模块同时跑!
我举个例子你就明白了。假设有两个always块:
// 并行执行的两个always块
always @(posedge clk) begin
a <= b;
end
always @(posedge clk) begin
b <= a;
end
在软件里,这会产生死循环。但在硬件里,每个时钟沿,a和b同时交换值。为什么会这样?因为这两个always块对应的是两个独立的寄存器,它们并行工作。
再来看顺序语句。always块内部的begin...end,是顺序执行的。但要注意,这里的“顺序”只在一个时钟周期内有效:
always @(posedge clk) begin
temp = a + b; // 先计算
c = temp + d; // 再用结果
end
这里用的是阻塞赋值“=”,所以顺序执行。如果换成非阻塞赋值“<=”,结果就完全不同了。
避坑指南:我曾经在一个项目中,把组合逻辑里的阻塞赋值和非阻塞赋值混用了。结果仿真没问题,上板子就乱跳。查了两天才发现,是赋值方式用错了。记住:时序逻辑用非阻塞“<=”,组合逻辑用阻塞“=”。
为了帮你理清思路,我画了张图:
这张图把三个核心知识点串起来了。你仔细看,左边是语言选择,中间是设计方法,右边是执行机制。三者缺一不可。
最后说一句心里话:学硬件描述语言,别死记语法。多想想“这个代码综合出来是什么电路”。我见过太多人,写出来的代码仿真没问题,综合出来一堆锁存器。嗯,这就是没理解并行和顺序的区别。
好了,这一章就到这里。记住:Verilog和VHDL只是工具,模块化是方法,并行思维是核心。把这三点吃透了,后面的路就好走了。
本章要点回顾:
- Verilog适合快速开发,VHDL适合严谨项目
- 模块化设计要遵循“单一职责”原则
- 并行是硬件的本质,顺序是局部的行为
- 阻塞赋值和非阻塞赋值不能混用