3. 硬件描述语言基础:模块化设计、数据类型与运算符、always/process块、结构化建模
各位同学,欢迎来到硬件加速设计的第三站。今天我们要聊的,是硬件描述语言(HDL)的核心基础。说白了,Verilog 和 VHDL 就是我们和硬件对话的“普通话”。你想想看,要让一堆硅片按照你的想法干活,总得有个沟通方式吧?
我个人习惯把 HDL 比作“硬件图纸的编程语言”。它不像 C 语言那样一条一条顺序执行,而是描述一堆电路同时干活。嗯,这个思维转换,是很多新手的第一道坎。我在项目中带过不少新人,他们总爱问:“为什么我写的 Verilog 仿真没问题,上板就乱跑?” 答案往往就出在今天要讲的这几个基础点上。
3.1 模块化设计:搭积木的艺术
模块化,说白了就是“分而治之”。一个复杂的芯片,你不可能在一个文件里写完所有逻辑。那会乱成一锅粥。我们把它拆成一个个小模块,每个模块干一件具体的事。
在 Verilog 里,模块用 module 和 endmodule 包起来。VHDL 则是 entity 加 architecture。我建议你从一开始就养成好习惯:一个文件只放一个模块。这样后期维护、复用都方便。
举个例子,一个简单的 D 触发器模块:
// Verilog 模块示例
module d_flip_flop (
input wire clk,
input wire rst_n,
input wire d,
output reg q
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= 1'b0;
else
q <= d;
end
endmodule
你看,这个模块的接口(clk, rst_n, d, q)就是它的“名片”。别人用它的时候,只需要知道这些接口怎么接,不需要关心内部怎么实现。这就是模块化的魅力。
3.2 数据类型与运算符:硬件的“砖块”和“水泥”
数据类型,就是你要操作的对象。Verilog 里最常用的是 wire 和 reg。VHDL 里则是 signal 和 variable。
简单粗暴的理解:
wire(Verilog)/signal(VHDL):相当于一根导线,只能被赋值一次,用于组合逻辑。reg(Verilog)/variable(VHDL):相当于一个存储单元,可以在 always/process 块里反复赋值,用于时序逻辑。
我记得有一次,一个同事把 wire 类型用在了 always 块里,结果综合报错,他查了半天没找到原因。嗯,这就是基础不牢的后果。
运算符方面,HDL 和 C 语言很像,但有几个坑要注意:
| 运算符类型 | Verilog 示例 | VHDL 示例 | 说明 |
|---|---|---|---|
| 算术 | + - * / |
+ - * / |
乘法会消耗大量 LUT,慎用 |
| 逻辑 | && || ! |
and or not |
用于条件判断 |
| 位运算 | & | ~ ^ |
and or not xor |
按位操作,硬件加速常用 |
| 拼接 | {a, b} |
a & b(注意不同) |
Verilog 的拼接非常灵活 |
| 移位 | << >> |
sll srl |
移位在硬件里就是连线,不耗资源 |
/ 除法运算符,结果综合出来的电路面积大得吓人,时序也跑不过。后来我改用移位和查找表(LUT)来实现除法,效果好了很多。记住:硬件里除法很贵,能不用就不用。
3.3 always/process 块:硬件的“心脏”
这是 HDL 最核心的部分。Verilog 的 always 块和 VHDL 的 process 块,就是描述电路行为的地方。
两种基本用法:
- 组合逻辑: 敏感列表里只包含输入信号,赋值用阻塞赋值(
=)。 - 时序逻辑: 敏感列表里包含时钟沿和异步复位,赋值用非阻塞赋值(
<=)。
你想想看,为什么要有阻塞和非阻塞的区别?说白了,这是为了模拟真实硬件的并行行为。非阻塞赋值 <= 会在所有语句都计算完后再统一更新,就像真实的寄存器一样。
看一个 VHDL 的例子:
-- VHDL process 示例
process(clk, rst_n)
begin
if rst_n = '0' then
q <= '0';
elsif rising_edge(clk) then
q <= d;
end if;
end process;
这个 process 块描述的就是一个带异步复位的 D 触发器。注意看,敏感列表里同时有 clk 和 rst_n,因为复位信号是异步的,必须随时响应。
3.4 结构化建模:把模块拼起来
结构化建模,就是模块化设计的进阶。你把写好的小模块,像搭积木一样拼成更大的系统。这在硬件加速设计中非常常见,比如你要做一个图像处理加速器,就可以把滤波模块、缩放模块、色彩转换模块拼在一起。
Verilog 里用实例化(instantiation)来实现:
// 顶层模块,实例化两个 D 触发器
module top (
input wire clk,
input wire rst_n,
input wire data_in,
output wire data_out
);
wire mid;
// 实例化第一个 D 触发器
d_flip_flop u0 (
.clk (clk),
.rst_n(rst_n),
.d (data_in),
.q (mid)
);
// 实例化第二个 D 触发器
d_flip_flop u1 (
.clk (clk),
.rst_n(rst_n),
.d (mid),
.q (data_out)
);
endmodule
你看,通过 u0 和 u1 两个实例,我们把两个 D 触发器串成了一个 2 级流水线。这就是结构化建模的精髓:重用已有的模块,通过连线组合出更复杂的功能。
VHDL 里用 component 声明和 port map 来实现,思路是一样的。
好了,今天的内容就到这里。硬件描述语言的基础,说白了就是这三板斧:模块化设计让你能拆解问题,数据类型和运算符让你能描述数据,always/process 块让你能描述行为,结构化建模让你能组合系统。把这些基础打牢,后面学状态机、流水线、接口协议就会轻松很多。
记住,写 HDL 和写软件最大的区别是:你写的每一行代码,最终都会变成实实在在的电路。所以,下笔之前,先在脑子里过一遍:这个电路长什么样?信号怎么流?时序对不对?嗯,养成这个习惯,你离资深工程师就不远了。