FPGA安路赛项第一讲:数字逻辑与verilog基础(上)笔记

FPGA 与数字电路设计通关笔记


目录

  1. FPGA 硬件开发范式与思维转变
  2. 数字电路硬件基石与编码机理
  3. Verilog RTL 语法精要与编译器约束
  4. 组合逻辑隐性陷阱与工业级设计规范
  5. 典型电路建模实战:3-8 译码器

一、 FPGA 硬件开发范式与思维转变

FPGA 设计本质上是**“用代码画原理图”**,与 C++ 等软件开发有着根本性的区别。

维度 软件开发(C++) 硬件开发(Verilog RTL)
执行机制 顺序执行(Sequential) 绝对并发(Concurrent,所有门电路同时上电工作) [1]
例化本质 动态内存分配(在 RAM 堆栈上分配地址空间) 静态硅片分配(在芯片上消耗具体的物理门/LUT资源)
互连信号 变量(读取/写入内存) 物理导线(Net,具有传播延迟 $t_{pd}$) [1]
调试手段 打印信息(printf)、单步调试(Debug) 仿真时序波形图(Waveforms,通过 GTKWave/ModelSim 观察)

二、 数字电路硬件基石与编码机理

1. 为什么计算机和 FPGA 统一使用“补码”?

  • 物理痛点:若使用“原码”或“反码”做减法,电路内部需要比较器、减法器、加法器和多路选择器,导致物理网表臃肿、时延大。
  • 物理实现(补码减法):补码将减法转化为加法:$A - B = A + (-B) = A + \bar{B} + 1$。
  • 极简硬件实现:在多位全加器中,只需将输入 $B$ 按位取反,并将最低位进位输入 $C_{in}$ 强制接高电平 1,即可直接复用同一个加法器完成减法,节省了大量硬件面积。

2. 异或门(XOR)的“受控反相器”物理机制

在逻辑运算(^)中,异或门是天然的控制门:

  • 当控制端 $sel = 0$ 时:$y = b \oplus 0 = b$(数据原样输出);
  • 当控制端 $sel = 1$ 时:$y = b \oplus 1 = \bar{b}$(数据按位取反,变为反码)。
  • 应用:该电路作为补码加减法控制电路的核心,能动态选择是做加法还是做减法。

3. D 触发器(D-FF):时序逻辑与记忆的源头

  • 快照机制:D 触发器是边沿敏感(Edge-Sensitive)器件。它只在时钟信号 CLK 从 0 变到 1(正边沿 posedge)的一瞬间锁存输入 $D$ 的状态,并将该状态传送到输出 $Q$。
  • 状态保持:在非上升沿期间,无论输入 $D$ 如何变化,输出 $Q$ 保持前一次快照的值不变。这是硬件存储和“记忆”的基础。

4. 寄存器(Register):D 触发器的并排与时钟同步

  • 定义:为了存储多位宽的数据(如 8-bit char 或 32-bit int),硬件上将多个 DFF 并排排列。
  • 共享时钟(Shared CLK):为了防止数据更新时产生**时钟偏斜(Clock Skew)**和数据“参差不齐”的混乱,必须让所有 DFF 共享同一个 CLK 信号源,以保证它们在物理上的同一个皮秒瞬时同步更新。

三、 Verilog RTL 语法精要与编译器约束

1. 模块结构(Module & Endmodule)

  • 物理映射:一个 module 对应一颗集成电路(IC)芯片的边界外壳
  • 端口(引脚)类型
    • input:输入端口,信号只能单向流入,物理属性强制为线网型(wire)。
    • output:输出端口,信号向外输出。
    • inout:双向端口,多用于总线收发,内部需要高阻态(Z)逻辑进行物理控制。

2. 数据类型:wire(线网)与 reg(寄存器)

  • wire:代表物理连接导线,只能用 连续赋值语句 assign 驱动 [1]。其左值(LHS)必须是 wire 型 [1]。
  • reg:在行为描述中用于锁存状态,必须用 过程赋值语句(在 always / initial 块中) 驱动 [1]。其左值必须是 reg 型 [1]。
  • 【物理误区】reg 不等于物理寄存器
    • 组合过程块 always @(*) 中,reg 变量会被综合器映射为纯组合逻辑电路(导线和门) [1];
    • 时序过程块 always @(posedge clk) 中,reg 变量才会被综合器映射为物理 D 触发器(DFF) [1]。

3. 子程序复用:functiontask

维度 函数(function 任务(task
仿真延迟 严格为零延迟,禁止使用任何时延或事件控制(#, @, wait) [1.1.9] 允许非零延迟,可以包含时效控制(常用于产生总线周期) [1.1.5]
端口规则 至少一个 input;禁止使用 output / inout 任意数量(包括零个)的 input, output, inout [1.1.5]
可综合性 高度可综合,映射为纯组合逻辑电路 [1.1.1, 1.1.9] 不含时延时可综合;若含时延则绝对不可综合(常用于 Testbench) [1.1.1, 1.1.9]
应用场景 算术转换、CRC 计算、编码/译码器封装 仿真测试平台中的协议发送、总线读写行为建模
安全机制 并发调用时,可使用 automatic 关键字将其转为可重入的动态局部堆栈存储 [1.1.3, 1.1.5]

4. 组合逻辑的两种描述范式

  • 连续赋值 assign
    • 用于数据流建模。只要右值(RHS)发生变化,仿真器便会激活该语句重新计算(右值敏感事件驱动),瞬时传递给左值(LHS)。
  • 组合过程块 always @(*)
    • 用于行为级建模。括号中的 (*) 表示隐式通配符敏感列表,编译器会自动将所有右值信号加入监听,彻底规避了因敏感信号漏写而产生非预期锁存器(Latch)的物理隐患 [1]。

四、 组合逻辑隐性陷阱与工业级设计规范

1. 隐藏陷阱:意外锁存器(Unintended Latch)的引入

  • 成因:在组合逻辑过程块(always @(*))中,如果条件分支未覆盖全(如 if 缺少 else,或者 case 缺少 default),在条件未满足时,语法要求变量“保持旧值” [1]。由于组合逻辑门没有记忆功能,综合工具被迫插入一个电平敏感的锁存器(Latch) [1]。
  • Latch 危害
    1. 静态时序分析(STA)灾难:会引入组合反馈环路,导致时序分析路径不闭合,使时序收敛失败。
    2. 毛刺敏感:容易将组合电路的竞争冒险毛刺透传锁存,引发亚稳态。

2. 规避 Latch 的两大标准设计范式

  • 规范一:分支完备法
    • 在写组合逻辑时,确保 if 必须配对 elsecase 必须配对 default [1]。
  • 规范二:默认赋值法(Default Assignment) —— 工业级最推荐写法
    • always @(*) 块的最开头对被赋值信号赋予初始默认电平。根据阻塞赋值(=)的覆盖规则,后续即使分支条件不全,变量在任何情况下也都有确定值,从根本上完全杜绝 Latch 的生成

      always @(*) begin
          y = 1'b0; // 默认赋值,规避一切 Latch 隐患
          if (sel) begin
              y = a;
          end
      end
      

五、 典型电路建模实战:3-8 译码器

基于上述所有设计原则,标准的工业级 3-8 译码器实现如下:

// 3-8 译码器模块声明
module decoder3_8 (
    input  wire [2:0] sel, // 3位二进制输入编码网线
    output reg  [7:0] y    // 8位独热码输出寄存器(由于在always块内部赋值,必须声明为reg型)
);

    // 组合逻辑行为级描述(使用case平行译码选择网络,延迟对称)
    always @(*) begin
        case (sel)
            3'd0:    y = 8'b0000_0001; // 独热码特征:同一时间只有一位为1
            3'd1:    y = 8'b0000_0010;
            3'd2:    y = 8'b0000_0100;
            3'd3:    y = 8'b0000_1000;
            3'd4:    y = 8'b0001_0000;
            3'd5:    y = 8'b0010_0000;
            3'd6:    y = 8'b0100_0000;
            3'd7:    y = 8'b1000_0000;
            // 因为 Verilog 信号具有四值逻辑(0, 1, X, Z),必须配齐 default 
            // 才能彻底防止 sel 在不定态(X)或高阻(Z)时触发意外 Latch
            default: y = 8'b0000_0000; 
        endcase
    end

endmodule

这份笔记包含了从 FPGA 开发工具链对比到基本组合/时序单元、一直到完整的 3-8 译码器实现的全部核心知识点。你可以将其作为日常查阅的手册。