Verilog 与 C语言:Verilog 描述硬件

用Verilog 描述硬件。

如果你已经掌握微控制器编程,这是最难纠正的思维习惯之一。虽然 Verilog 语法看似是顺序执行的代码,但它的本质更接近 74 系列逻辑门电路,而非 C 语言编程。

利用门级原语理解 Verilog 的并行性

我们从数字逻辑课程中的 “积之和(Sum-of-Products)” 示例入手(清单 1、图 1)。本示例中,我们使用 Verilog 门级原语(Gate Primitives)实现逻辑表达式𝑌 =∑𝑚(2,4,5).

这些 Verilog 门级原语在写法上类似 C 语言函数:先指定门类型,后接布尔输出参数,最后是输入参数。例如:and (Out, in_1, in_2, in_3)

注意:它们看似函数,实则并非函数。这些代码不会 “运行”,而是直接对应 “硬件实体” 的存在。

清单 1:使用门级原语构建的 Verilog 积之和硬件描述代码。

图 1:对最小项 2、4、5 敏感的经典 3 位积之和电路

技术小贴士:清单 1 和图 1 仅作原理说明。实际工程中没人会这么写代码 —— 这种方式耗时且极易出错。更优实现方式请参考清单 2。

这是Verilog描述的纯组合电路。一切都同时存在;没有时钟,也没有寄存器。这几乎与使用74HC04十六进制逆变器、三组3输入74HC11 AND和三组3输入74HC4075 OR门构建电路完全相同。

积之和硬件的标准 Verilog 写法

清单 2 给出了积之和电路的推荐 Verilog 实现方式。相比清单 1,该写法更简洁、出错概率更低。不过,我们预期 FPGA 综合工具针对同一型号 FPGA,最终生成的网表与清单 1 几乎一致。无论哪种写法,综合工具都会将逻辑优化为 “资源占用最小、传播延迟最低” 的 FPGA 配置比特流,兼顾逻辑结构与时序效率。

技术小贴士:若你仍以 “微控制器思维” 看待 FPGA,可简单理解为:FPGA 的比特流相当于一份 “配置外设的巨型表格”。

module sop3_standard (
    input  wire C, B, A,   // LSB to MSB
    output wire Y
);

    assign Y = (~A &  B & ~C) |  // minterm 2 (010)
               ( A & ~B & ~C) |  // minterm 4 (100)
               ( A & ~B &  C);   // minterm 5 (101)

endmodule

清单 2:积之和电路的推荐实现代码。

FPGA 查找表(LUT)的常见误解及时序影响

FPGA 并非 “逻辑门的集合”,其核心构建单元是查找表(LUT)。本文中的积之和逻辑最终会被映射到单个 LUT 中 —— 事实上,现代 LUT 的输入位宽远大于 3 位,因此该逻辑还会留有冗余空间。例如,Terasic DE23-Lite上的 Altera Agilex 3 A3CZ135BB18AE7S采用了8输入LUT,如图2所示

回看图 1 的电路:因反相器传播延迟更长,存在经典的 “毛刺风险”。而 LUT 会将整个积之和逻辑整合为一个单元,可最大程度降低该风险(并非完全消除时序毛刺,但能显著减少)。对于包含多个 LUT 的大型设计,使用寄存器实现会更优。

图 2:Agilex 3 FPGA 自适应逻辑模块(ALM)框图。

寄存器和时钟信号规范FPGA硬件

直到现在,我们一直假设FPGA中的所有事情都是同时发生的。我们现在从组合逻辑转向顺序逻辑,引入了时钟和触发器,也称为寄存器或同步存储器。

这一区别可见于图2。举个简单的例子,我们看到最顶端的输出信号直接取自LUT,而下一个输出信号取自顶部的触发器(Reg)。这些分别代表组合输出和同步输出。

  • 组合输出会不断更新。
  • 寄存器在时钟信号的上升沿上更新。

例如,Terasic板使用可编程的德州仪器LMK3C0105A05RERR向FPGA提供三个时钟信号。

清单 3 是同步逻辑的 Verilog 代码,核心变化是:① 使用非阻塞赋值运算符 <=;② 敏感列表仅响应时钟上升沿(posedge clk)。结合图 2 来看:LUT 仍用于执行积之和运算,但结果会存入寄存器 —— 寄存器的值与时钟信号同步。

module sop3_registered (
    input  wire clk, C, B, A,
    output reg Y
);

    always @(posedge clk) begin
        Y <= (~A &  B & ~C) | 
             ( A & ~B & ~C) | 
             ( A & ~B &  C);
    end

endmodule

清单 3:带寄存器输出的积之和硬件描述代码。
仿真舒适区谬误:当 Verilog 不再描述硬件

我们专注于 “可综合逻辑”—— 这类代码能转换为配置 FPGA 的比特流。但 Verilog 并非仅用于此:你很快会接触到 Verilog 测试平台(Testbench)。测试平台不运行在 FPGA 上,而是运行在电脑上,因此编译后的测试平台可实现 FPGA 无法完成的功能,核心差异是 “时间控制” 和 “循环能力”。例如,可编写测试平台生成 N 个时钟周期的激励信号。

关键原则:若一段 Verilog 代码 “有编程的味道”,它大概率无法被综合。

区分硬件代码与测试平台代码

清单 4 能体现测试平台的典型特征:包含仿真时间尺度(timescale)、以 tb(testbench)为前缀的命名、表示 5 个时间单位延迟的 #5


THIS WILL NOT SYNTHESIZE INTO AN FPGA BITFILE.

`timescale 1ns/1ps

initial begin
    clk = 0;
    {A, B, C} = 3'b010; // Apply minterm 2

    for (i = 0; i < 10; i = i + 1) begin
        #5 clk = ~clk; // Rising edge - Y updates here!
        #5 clk = ~clk; // Falling edge
    end

    // Add code to test all minterms.

    $stop;
end

清单 4:Verilog 测试平台片段(不可综合)。

技术小贴士:后续可探讨 Verilog 生成器(Generator)—— 它们看似 “编程代码”,实则是 “迭代式硬件生成器”,可便捷地例化 N 个相同模块。

相关文章