第四章:硬件描述语言(HDL)进阶

说实话,很多做FPGA的朋友,写了好几年代码,还是停留在“把C语言思维翻译成Verilog”的阶段。我当年也是这样,直到有一次项目调试了整整两周,最后发现是一个不可综合的for循环在作怪。从那以后,我才真正开始认真对待HDL的进阶知识。

这一章,我们就来聊聊Verilog和VHDL里那些真正重要的东西。不是语法手册,而是实战中你必须搞明白的核心概念。

4.1 可综合与不可综合代码:这条红线在哪?

先问个问题:你写的代码,最终是要变成硬件电路的,对吧?那有些语法天生就是给仿真用的,没法变成真正的门电路。这就是可综合与不可综合的分界线。

核心原则:可综合代码描述的是硬件结构,不可综合代码描述的是测试行为。

我个人习惯把不可综合的代码归为三类:

  • 时间控制类:比如 #10waitdelay。硬件里哪有“等10个时间单位”这种操作?
  • 文件操作类:$readmemh$fopen$display。这些是仿真器用的,综合器根本不认识。
  • 动态资源类:new、动态数组、递归函数。硬件资源是静态分配的,没法“运行时创建”。

嗯,这里要注意:initial块在ASIC设计里通常不可综合,但在某些FPGA工具里可以用于初始化寄存器。我建议你统一把它当成不可综合的,用复位信号来做初始化,这样移植性更好。

避坑指南:我曾经在一个项目中,用for循环写了复杂的地址译码逻辑。仿真完全正确,但综合后资源爆增10倍。后来才发现,综合器把循环展开了,生成了大量并行逻辑。正确的做法是用case语句或者查找表。

4.2 行为级 vs 结构级:两种思维模式

你想想看,行为级描述就像是在说“我要一个能算加法的东西”,而结构级描述则是在说“我要把这个加法器和那个寄存器连起来”。

我刚开始学的时候,觉得行为级多爽啊,几行代码搞定。但后来发现,真正的高手是两种描述方式混着用的。

描述方式 特点 适用场景 代码量
行为级 关注功能,不关注具体实现 算法模块、控制逻辑
结构级 关注连接,精确控制硬件 顶层模块、IP核例化
数据流级 关注信号流动,assign语句为主 组合逻辑、简单运算 中等

举个例子,一个简单的D触发器:

// 行为级描述
always @(posedge clk or negedge rst_n) begin
  if (!rst_n)
    q <= 1'b0;
  else
    q <= d;
end

// 结构级描述(例化原语)
FDCE #(.INIT(1'b0)) dff_inst (
  .Q(q),
  .C(clk),
  .CE(1'b1),
  .CLR(~rst_n),
  .D(d)
);

行为级代码更易读,但结构级代码让你对底层硬件有绝对控制权。我在做高速接口设计时,经常用结构级来精确控制时序路径。

4.3 Verilog与VHDL语法精要:别纠结,都学

说实话,这两者之争就像iOS和Android,各有拥趸。我的建议是:主攻一个,但能读懂另一个。

我个人更习惯Verilog,因为它的语法更简洁,写起来快。但VHDL在大型项目中确实更严谨,不容易出错。

小技巧:如果你用Verilog,记住这几个容易出错的点:

  • reg类型不一定生成寄存器,wire也不一定是组合逻辑
  • 阻塞赋值=和非阻塞赋值<=混用是大忌
  • 敏感列表不完整会导致仿真和综合结果不一致

VHDL里我特别想提的是std_logicstd_logic_vector。很多人喜欢用bit类型,但std_logic支持'Z'、'X'等状态,在仿真时能帮你发现很多问题。

4.4 常用IP核的RTL级理解

做FPGA逆向工程,你迟早要面对各种IP核。我的经验是:不要只看黑盒,要理解它的RTL实现。

常见的IP核包括:

  • FIFO:同步还是异步?空满标志怎么生成?格雷码跨时钟域怎么做的?
  • RAM/ROM:单口、双口、真双口有什么区别?读优先还是写优先?
  • PLL/DLL:锁相环的基本结构,相位对齐的原理
  • SerDes:串并转换,8B/10B编码,时钟恢复

我记得有一次逆向一个通信板卡,里面用了一个复杂的FIFO。我花了三天时间,从RTL代码里一点点还原出它的读写指针逻辑,最后发现它其实就是一个标准的异步FIFO,只是加了一些自定义的状态标志。

核心思路:理解IP核的RTL实现,就是理解它的状态机、数据通路和控制逻辑。不要被复杂的接口吓到,拆解成小模块逐个击破。

4.5 知识体系总览

下面这张图,是我自己整理的HDL进阶知识体系。你可以把它当成学习路线图,也可以当成复习时的检查清单。

HDL进阶知识体系 可综合与不可综合 时间控制类 文件操作类 动态资源类 三种描述方式 行为级描述 结构级描述 数据流级描述 常用IP核RTL理解 FIFO(同步/异步) RAM/ROM PLL/SerDes 核心能力:从代码到硬件的思维转换 看得懂代码 → 想得到电路 → 做得出优化 三者相互关联:可综合性决定写法,描述方式决定风格,IP核理解决定深度

这张图把本章的核心内容串起来了。你仔细看,三个分支其实是相互关联的。比如,你写一个FIFO的RTL代码,既要考虑可综合性(不能用#delay),又要选择合适的描述方式(控制逻辑用行为级,数据通路用结构级)。

好了,这一章的内容就到这里。记住,HDL不是编程语言,是硬件描述语言。你写的每一行代码,最终都会变成实实在在的电路。带着这个意识去写代码,你的水平会提升得很快。

专注资料整理