第三讲:硬件描述语言(HDL)入门——Verilog与VHDL的选择、Verilog基本语法结构
各位同学,欢迎来到第三讲。
从这一讲开始,我们就要真正动手写代码了。说实话,很多初学者觉得FPGA难,其实不是难在硬件本身,而是卡在了「怎么把脑子里的电路图,翻译成电脑能懂的文本」这一步。今天我们就来解决这个问题。
3.1 Verilog还是VHDL?我的选择
这是个老生常谈的问题,也是每个新手都会纠结的问题。我当年入行时也纠结过,还特意花了两周时间把VHDL啃了一遍。结果呢?进了项目组发现大家都在用Verilog。嗯,这就是现实。
简单说下两者的区别:
| 对比项 | Verilog | VHDL |
|---|---|---|
| 语法风格 | 类似C语言,简洁 | 类似Ada,严谨啰嗦 |
| 学习曲线 | 上手快,容易写 | 门槛高,但不易出错 |
| 行业应用 | 美国、中国、IC设计主流 | 欧洲军工、航天领域较多 |
| 数据类型 | reg/wire,相对简单 | signal/variable,类型严格 |
| 代码量 | 同样功能,代码更短 | 同样功能,代码更长 |
我个人建议:如果你是做工业控制、通信、图像处理这些主流方向,直接学Verilog。为什么?因为市面上90%的IP核、参考设计、开源项目都是Verilog写的。你想想看,拿到一个现成的模块,结果发现是VHDL写的,你还得先看懂再改,多麻烦。
当然,如果你以后打算去欧洲做军工项目,或者进某些特定的研究所,VHDL还是得补上。但作为入门,Verilog足够了。
3.2 Verilog的基本语法结构
好了,我们直接上手。Verilog代码的基本单元叫「模块(module)」。你可以把它想象成一个黑盒子——有输入、有输出、里面装着具体的逻辑电路。
3.2.1 模块的定义
一个最简单的模块长这样:
module and_gate (
input wire a, // 输入端口a
input wire b, // 输入端口b
output wire y // 输出端口y
);
assign y = a & b; // 连续赋值,实现与门
endmodule
看到了吗?结构非常清晰:
- module 关键字后面跟模块名(and_gate)
- 括号里是端口列表,声明了输入输出
- endmodule 表示模块结束
我在项目中遇到过很多新手,写模块时经常忘记写端口方向。结果综合时报错,找了半天才发现是input/output写漏了。嗯,这种坑踩一次就够了。
3.2.2 端口声明
端口有三种方向:
- input:输入端口,数据从外部流入模块内部
- output:输出端口,数据从模块内部流出
- inout:双向端口,既能输入也能输出(比如I2C的数据线)
端口的数据类型主要有两种:
- wire:线网类型,用于组合逻辑赋值(assign语句)
- reg:寄存器类型,用于时序逻辑赋值(always语句)
3.2.3 赋值语句
Verilog里有两种赋值方式,这是最核心的概念之一:
1. 连续赋值(assign)
用于组合逻辑,输入一变,输出立刻变。就像一根导线:
assign y = a & b; // a和b变化时,y立即变化
2. 过程赋值(always块内)
用于时序逻辑或组合逻辑,需要放在always块里:
// 时序逻辑:上升沿触发
always @(posedge clk) begin
q <= d; // 非阻塞赋值,在时钟上升沿更新
end
// 组合逻辑:电平敏感
always @(*) begin
y = a & b; // 阻塞赋值,立即更新
end
这里有个关键点:阻塞赋值(=)和非阻塞赋值(<=)。我刚开始学的时候,觉得这两个差不多,结果写出来的电路仿真和实际综合完全不一样。
- 时序逻辑(always @(posedge clk))用 <=(非阻塞赋值)
- 组合逻辑(always @(*) 或 assign)用 =(阻塞赋值)
这个规则我建议你死记硬背,不要问为什么。等你写了几万行代码,自然就懂了。
3.3 一个完整的例子:D触发器
光说不练假把式。我们写一个真正的D触发器,带异步复位:
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) begin
q <= 1'b0; // 复位时输出0
end else begin
q <= d; // 时钟上升沿采样d
end
end
endmodule
这个例子包含了:
- 模块定义和端口声明
- 时序逻辑的always块写法
- 异步复位的处理(敏感列表里同时有clk和rst_n)
- 非阻塞赋值的使用
你想想看,这个模块综合出来是什么?就是几个查找表加一个触发器。硬件描述语言,说白了就是用文本描述电路结构。你写的每一行代码,最终都会变成实实在在的硅片上的晶体管。
3.4 小结
今天我们讲了三个核心点:
- Verilog vs VHDL:入门选Verilog,主流方向够用了
- 模块结构:module - 端口声明 - 内部逻辑 - endmodule
- 赋值方式:连续赋值用assign,时序逻辑用非阻塞赋值,组合逻辑用阻塞赋值
下一讲我们会深入讲always块的更多用法,以及如何用Verilog描述更复杂的电路。记住,写Verilog不是写软件,你脑子里想的应该是「这个信号怎么走线」「这个触发器什么时候采样」。保持这个思维,你很快就能上手。
好了,今天就到这里。有问题随时交流。
公众号:蓝海资料掘金营,微信deep3321