3. 硬件基础回顾:FPGA架构、ASIC设计流程、硬件描述语言核心概念

好,咱们正式开始聊硬件。很多软件工程师转硬件,第一反应是「这玩意儿怎么这么别扭?」。我完全理解。当年我第一次接触Verilog,满脑子都是C语言的if-else,结果综合出来的电路让我哭笑不得。今天这一章,咱们把三个最基础的东西捋清楚:FPGA到底长什么样、ASIC是怎么造出来的、以及硬件描述语言那点「反直觉」的核心概念。

3.1 FPGA架构:你手里的「乐高积木」

FPGA,说白了就是一堆可以随意连线的逻辑门和触发器。你想想看,如果给你一箱乐高,你可以搭房子、搭汽车、搭城堡——FPGA也是这个道理。只不过它的「积木块」是预先做好的,你通过编程来决定它们怎么连。

核心组成:

  • CLB(可配置逻辑块):这是最基础的运算单元。每个CLB里包含几个LUT(查找表)和触发器。LUT本质上是一个小RAM,你用输入地址去查表,输出就是结果。我习惯把LUT理解成「硬编码的真值表」。
  • BRAM(块RAM):片上的存储资源。注意,FPGA里的BRAM是真正的双端口RAM,读写可以同时进行。我在项目中遇到过,有人用分布式寄存器搭了个大FIFO,结果资源爆了——换成BRAM,面积直接缩到十分之一。
  • DSP Slice(数字信号处理单元):专门做乘加运算的硬核。做滤波、FFT的时候,这东西比用LUT拼出来的乘法器快一个数量级。
  • IOB(输入输出块):负责和外界打交道。支持各种电平标准,比如LVCMOS、LVDS、SSTL。嗯,这里要注意:IOB的驱动能力是有限的,别想着用普通IO去驱动大功率器件。
  • 布线资源:连接以上所有东西的「高速公路」。布线延迟是FPGA时序问题的头号元凶。

我画了一张FPGA内部结构的简化图,你看一眼就明白了:

FPGA 内部架构简化示意图 IOB 输入输出块(四周环绕) CLB LUT + FF CLB LUT + FF CLB LUT + FF CLB LUT + FF CLB CLB CLB CLB BRAM 18Kb DSP 乘加器 BRAM 18Kb DSP 乘加器 CLB CLB CLB CLB CLB BRAM DSP IOB 布线

避坑指南:我曾经犯过一个低级错误——在FPGA里用LUT实现了一个64位加法器,结果布线延迟大到时序收敛不了。后来换成DSP Slice做加法,频率直接翻倍。记住:能用硬核就别用软逻辑。

3.2 ASIC设计流程:从想法到芯片的「九九八十一难」

ASIC和FPGA最大的区别是什么?FPGA是「搭积木」,ASIC是「烧陶瓷」。积木搭错了可以重来,陶瓷烧坏了就得扔。所以ASIC的流程极其严谨,每一步都有对应的检查点。

我习惯把ASIC流程分成三个阶段:前端、中端、后端。咱们一个一个说。

阶段 主要工作 产出物 我的经验
前端设计 RTL编码、功能仿真、逻辑综合 门级网表 仿真通过不代表综合能过,我见过太多仿真完美但综合出一堆latch的案例
中端验证 形式验证、DFT插入、静态时序分析 带约束的网表 STA是门艺术,setup和hold的trade-off最考验功力
后端物理 布局布线、时钟树综合、物理验证 GDSII版图 后端决定了你的芯片能不能「活」下来,IR drop和EM是两大杀手

你可能会问:「为什么ASIC流程这么长?」原因很简单——流片一次几十万到上千万美元,没人敢马虎。我参与过一个项目,前端设计只花了3个月,后端迭代却用了半年。为什么?因为时钟树综合后时序总是差那么一点点,最后不得不改RTL架构。

重要提醒:ASIC和FPGA的设计思路有本质区别。FPGA里你不太关心布局布线,因为工具帮你做了。但ASIC里,物理设计直接影响性能和功耗。我建议初学者先从FPGA入手,等理解了时序和资源的概念,再碰ASIC。

3.3 硬件描述语言核心概念:Verilog/VHDL的「反直觉」之处

好,终于到了代码部分。很多软件工程师写Verilog,第一反应是「这不就是C语言吗?」——大错特错。硬件描述语言描述的是电路结构,而不是执行步骤。你写的是「连线」,不是「指令」。

3.3.1 并行 vs 顺序

软件是顺序执行的:第一行做完,再做第二行。硬件是并行执行的:所有always块、所有assign语句,在仿真开始的那一刻同时启动。我刚开始学的时候,写了一个这样的代码:

// 错误示例:以为会顺序执行
always @(posedge clk) begin
    a <= b;
    c <= a;  // 这里的a是旧值,不是b!
end

为什么会这样?因为非阻塞赋值(<=)是在时钟沿采样旧值,然后同时更新。所以c拿到的是a的旧值,而不是b。如果你想要流水线效果,这样写是对的;但如果你想要组合逻辑,就得用阻塞赋值(=)。

3.3.2 组合逻辑 vs 时序逻辑

这是新手最容易混淆的地方。我总结了一个简单规则:

  • 组合逻辑:输出只取决于当前输入。用assignalways @(*)实现。没有时钟,没有记忆。
  • 时序逻辑:输出取决于当前输入和之前的状态。用always @(posedge clk)实现。有触发器,有记忆。

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

// 时序逻辑:D触发器
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        q <= 1'b0;
    else
        q <= d;
end

注意那个negedge rst_n——异步复位。我习惯用异步复位、同步释放的方式,这样可以避免复位释放时的亚稳态问题。

3.3.3 阻塞赋值 vs 非阻塞赋值

这个话题我能聊一整天。简单说:

赋值方式 符号 适用场景 行为
阻塞赋值 = 组合逻辑 立即更新,后面的语句看到新值
非阻塞赋值 <= 时序逻辑 时钟沿采样旧值,同时更新

黄金法则:写时序逻辑用非阻塞赋值,写组合逻辑用阻塞赋值。两者混用?我见过有人在一个always块里同时用=<=,综合出来的电路仿真和实际行为不一致,查了三天才找到原因。

3.3.4 有限状态机(FSM)

FSM是数字设计的核心。我习惯用三段式写法:

// 三段式FSM示例
// 第一段:状态转移
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        state <= IDLE;
    else
        state <= next_state;
end

// 第二段:次态逻辑(组合)
always @(*) begin
    case (state)
        IDLE:   if (start) next_state = RUN;
                else       next_state = IDLE;
        RUN:    if (done)  next_state = DONE;
                else       next_state = RUN;
        DONE:   next_state = IDLE;
        default: next_state = IDLE;
    endcase
end

// 第三段:输出逻辑(组合或时序)
assign busy = (state == RUN);

为什么用三段式?因为可读性强,而且综合工具容易优化。我曾经见过有人把状态机和输出逻辑写在一起,结果状态编码一变,输出时序全乱了。

3.4 从软件思维到硬件思维的转变

最后,我想聊聊思维方式的转变。软件工程师转硬件,最难的不是语法,而是「并行思维」。你写C语言时,脑子里是一条执行流;写Verilog时,脑子里应该是一张电路图。

我建议你这样做:

  • 每写一段代码,先画个时序图,看看信号什么时候变化
  • 多用仿真波形调试,别只盯着代码看
  • 记住:硬件设计是「空间换时间」——面积大往往意味着速度快

个人习惯:我每次写RTL之前,都会在白板上画一个模块框图,标清楚输入输出、时钟域、数据流。画清楚了再写代码,效率至少提高50%。

好了,这一章的内容就到这里。FPGA架构、ASIC流程、HDL核心概念,这三块是硬件设计的基石。下一章咱们会聊更具体的东西——但在此之前,我建议你把今天的内容消化掉。尤其是那个并行赋值的概念,想不明白的话,后面会很难受。


公众号:蓝海资料掘金营,微信deep3321