4、硬件描述语言基础:Verilog/VHDL基础语法、模块化设计思想、组合逻辑与时序逻辑
各位同学,咱们今天聊聊硬件描述语言。说白了,这就是硬件工程师的“编程语言”。你写的是代码,但最终跑在芯片上的是电路。这个思维转换,是入门的第一道坎。
我个人习惯用Verilog,因为它语法更灵活,写起来像C语言。VHDL则更严谨,像Ada。但不管用哪个,核心思想是一样的:描述硬件,而不是描述算法。
4.1 Verilog基础语法:从“线”到“寄存器”
先看一个最简单的例子。我想实现一个与门:
module and_gate (
input wire a, // 输入信号,用wire类型
input wire b,
output wire y // 输出信号
);
assign y = a & b; // 连续赋值,组合逻辑
endmodule
这里有个关键点:assign 语句是持续赋值的。只要a或b变了,y立刻跟着变。这跟软件里的“赋值”完全不同——软件是顺序执行,硬件是并行执行。
我在项目中遇到过新手把 assign 当成软件里的“=”来用,结果仿真死活不对。嗯,这里要注意:Verilog里的“=”是阻塞赋值,用于组合逻辑;而“<=”是非阻塞赋值,用于时序逻辑。这个区别,后面会细讲。
4.2 VHDL基础语法:更“啰嗦”但更安全
同样的与门,用VHDL写是这样的:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity and_gate is
Port ( a : in STD_LOGIC;
b : in STD_LOGIC;
y : out STD_LOGIC);
end and_gate;
architecture Behavioral of and_gate is
begin
y <= a and b; -- 并发信号赋值
end Behavioral;
你看,VHDL把“实体”(entity)和“架构”(architecture)分开了。实体定义接口,架构定义功能。这种分离设计,在大型项目中特别有用——接口稳定了,内部随便改。
我个人觉得VHDL的语法虽然啰嗦,但可读性更好。尤其是 STD_LOGIC 类型,它不止有'0'和'1',还有'Z'(高阻)、'X'(未知)等状态。这在仿真时能帮你发现很多潜在问题。
4.3 模块化设计思想:搭积木的艺术
你想想看,一个复杂的芯片可能有上亿个晶体管。如果全写在一个文件里,那调试起来简直是噩梦。所以,模块化是硬件设计的基石。
模块化设计有几个原则:
- 高内聚、低耦合:每个模块只做一件事,接口尽量简单
- 层次化:顶层模块只负责连接子模块,不实现具体逻辑
- 可复用:写好的模块,下次换个项目还能用
举个例子,我要做一个计数器模块:
module counter (
input wire clk,
input wire rst_n,
output reg [7:0] count
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
count <= 8'd0;
else
count <= count + 1'b1;
end
endmodule
这个模块只做一件事:在时钟上升沿计数。复位信号是低电平有效(rst_n 的 _n 后缀是我个人的命名习惯,一看就知道是低有效)。
然后顶层模块可以这样调用它:
module top (
input wire clk,
input wire rst_n,
output wire [7:0] led
);
counter u_counter (
.clk (clk),
.rst_n (rst_n),
.count (led)
);
endmodule
你看,顶层模块就像搭积木一样,把计数器模块“实例化”进来。这种设计方式,让代码结构清晰,也方便团队协作。我曾经在一个项目中,三个人同时开发不同的模块,最后在顶层一拼接,一次仿真通过——那种感觉,真的很爽。
4.4 组合逻辑与时序逻辑:硬件的“阴阳”两面
这是硬件设计最核心的概念。我刚开始学的时候也迷糊过,后来想明白了一个道理:
- 组合逻辑:输出只取决于当前输入,没有记忆功能。比如与门、或门、加法器。
- 时序逻辑:输出不仅取决于当前输入,还取决于之前的状态。比如触发器、计数器、状态机。
用Verilog实现组合逻辑,通常用 assign 或 always @(*):
// 组合逻辑:用 assign
assign sum = a + b;
// 组合逻辑:用 always @(*)
always @(*) begin
if (sel)
out = a;
else
out = b;
end
时序逻辑则必须用 always @(posedge clk):
// 时序逻辑:D触发器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= 1'b0;
else
q <= d;
end
这里有个避坑指南:我曾经犯过一个错误,在时序逻辑的 always 块里用了阻塞赋值(=)。结果仿真看起来没问题,但综合出来的电路多了一级不必要的锁存器。后来我养成了习惯:时序逻辑用非阻塞赋值(<=),组合逻辑用阻塞赋值(=)。这个规则,请你一定记住。
4.5 知识体系总览
下面这张图,是我自己总结的硬件描述语言学习路径。你可以把它当成一张地图,随时回来看看自己走到哪了。
核心要点总结:
- Verilog和VHDL都是硬件描述语言,不是编程语言——你写的是电路
- 模块化设计是工程化的基础,别把所有代码塞进一个文件
- 组合逻辑没有记忆,时序逻辑有记忆——这是硬件设计的“阴阳”两面
- 阻塞赋值(=)用于组合逻辑,非阻塞赋值(<=)用于时序逻辑——这个规则别搞反
我的小技巧:刚开始学的时候,可以先用Verilog写一些简单的组合逻辑(比如加法器、多路选择器),然后再加时钟变成时序逻辑(比如计数器、移位寄存器)。这样一步步来,思维转换会顺畅很多。
注意:千万不要在 always @(posedge clk) 块里用阻塞赋值!我曾经因为这个bug,花了两天时间才找到问题——仿真波形看起来完全正确,但综合出来的电路多了一个锁存器。后来我养成了习惯:写代码前先想清楚,这是组合逻辑还是时序逻辑,然后再动笔。
好了,这一章的内容就到这里。记住:硬件描述语言的核心不是语法,而是电路思维。你写的每一行代码,最终都会变成芯片上的晶体管。带着这个意识去学习,你会发现很多问题都迎刃而解。