这一篇在干嘛?

全书路线图:DSP 技术都用在哪儿、FPGA 与 ASIC/GDSP 的本质区别、Altera FPGA 的内部结构,以及一个贯穿全书的频率合成器设计实例。 原书代码为 VHDL,本篇所有代码已改写为 Verilog

  • 第1章
  • 绪论
  • 1.1 数字信号处理技术概述
  • 1.2 FPGA 技术
    1. 小颗粒度器件
    1. 中等颗粒度器件
    1. 大颗粒度器件
  • 1.3 DSP 的技术要求
  • FPGA 和可编程信号处理器
  • 1.4 设计实现
  • 例 1.1 VHDL 设计类型的比较
    1. 逻辑资源
    1. 额外的资源和路由
    1. 定时估算
  • 例 1.2 32 位加法器的速度
    1. 功耗
    1. 设计的编译
    1. 平面布置图
    1. 仿真
    1. 性能分析
    1. 软内核
    1. 参数化内核
    1. 硬内核
    1. 知识产权内核的比较和难题
    1. 基于知识产权内核的 NCO 设计
  • 1.5 练习
  • 1.22 给定如下 MATLAB 指令:

自测一下

1.1 数字信号处理技术概述

数字信号处理(Digital Signal Processing,DSP)是指用数字电路对信号做滤波、变换、压缩等运算的技术。所谓”信号”,就是携带信息的随时间变化的物理量——比如麦克风输出的话音电压、天线收到的无线电波形。早期这些处理全靠电阻、电容、运放搭成的模拟电路完成,而 DSP 用”采样 + 计算”的思路取代了它们。

DSP 为什么能取代模拟系统?核心优势有三点:其一,数字元器件对温度变化、器件老化和元件容差不敏感——模拟电路里两个标称相同的电阻可能差 5%,运算结果就跟着漂;数字电路里 1 就是 1,很少受物理条件影响。其二,随着工艺进入亚微米尺度,模拟芯片受噪声困扰越来越严重,而数字设计在集成度上反超模拟,于是紧凑、低功耗、低成本的数字产品成为主流。

DSP 的应用面极广,可以按领域粗略分类:

  • 通用领域:滤波与卷积、自适应滤波、谱估计与傅里叶变换;
  • 语音/音频:编解码、回声消除、噪声消除、混音、声音合成,甚至人工耳蜗的信号处理;
  • 图像:压缩解压、旋转、图像增强与识别;
  • 信息系统:调制解调器、移动电话、线路均衡、加密解密、无线局域网;
  • 控制与仪器:伺服控制、磁盘/发动机控制、机器人、雷达声呐、波形发生器。

DSP 发展史上有两个加速器值得记住。第一个是 1965 年 Cooley 和 Tukey 发表的快速傅里叶变换(FFT)算法——它把计算离散傅里叶变换(DFT)的运算量从 级降到约 级,让频域分析第一次变得”算得动”。第二个是 20 世纪 70 年代后期出现的可编程数字信号处理器(PDSP):它能在单个时钟周期内完成一次定点乘-累加(Multiply-Accumulate,MAC)运算,而同期基于冯·诺依曼结构的普通微处理器做同样的事要几十个周期。现代 PDSP 还集成了浮点乘法器、桶式移位器、片上存储器甚至 A/D、D/A 接口。

一个典型的 DSP 系统长什么样?请看图 1-1。模拟信号首先经过抗混叠滤波器,它的阻带从采样频率的一半()开始——根据采样定理,采样会以 为周期复制频谱,如果信号中有超过 的成分,这些”镜像频率”就会折叠到带内造成混叠失真,且混叠一旦发生便无法事后消除,所以必须在采样前用模拟滤波器拦住。接着是模数转换器(ADC),完成采样保持、量化和编码,把连续波形变成一串数字。然后由数字处理电路完成与原来模拟系统等效的运算,结果既可以直接存储(如存入 CD),也可以经数模转换器(DAC)还原成模拟输出(如音频信号)。

图 1-1 一个典型的 DSP 应用示例(模拟滤波 → ADC → 数字处理 → DAC)

1.2 FPGA 技术

超大规模集成(VLSI)电路可以按图 1-2 分类。其中 FPGA(现场可编程门阵列)属于现场可编程逻辑(Field-Programmable Logic,FPL)器件——FPL 的定义是:由大量可反复配置的小规模逻辑模块和互连元件构成的可编程器件。FPGA 本质上是一种专用集成电路(ASIC),但与传统 ASIC 有个关键区别:典型 ASIC 需要额外的半导体制造工序,这些工序带来性能和功耗优势,同时带来高昂的一次性工程成本(NRE)——在 40nm 工艺节点,NRE 大约是 400 万美元,只有产量足够大才摊得薄。而门阵列(由”与非门海”构成,用户用网表描述功能,最后靠金属层布线定型)属于半定制 ASIC;FPL 的设计者则完全掌控实现过程,不需要任何芯片制造设备,改一版设计只是重新下载一次配置文件,不会因流片而拖慢进度。

图 1-2 VLSI 电路的分类(FPGA 归属于现场可编程逻辑 FPL 一支)

1.2.1 按颗粒度分类

颗粒度”指基本逻辑模块的大小,它决定了模块之间需要多少布线(路由)资源。通常分三类:

1. 小颗粒度器件

以 Pilkington(曾授权给 Plessey 和 Motorola)为代表,基本单元只有一个与非门加一个锁存器(图 1-3(a))。与非门被称为”通用门”——任何二进制逻辑函数都能只用与非门搭出来。但代价是布线资源消耗极大:门阵列里靠额外金属层布线没什么问题,可编程结构里布线却成了瓶颈;而且搭一个简单 DSP 单元就要海量与非门——一个高速 4 位加法器约需 130 个与非门。算一笔账:实现一个 FIR 滤波器动辄需要几十个加法器和乘法器,若每个加法器都要上百个与非门,小颗粒度结构很快就撑不住了,因此它对大多数 DSP 算法没有吸引力。

图 1-3(a) 小颗粒度器件的基本逻辑模块:一个与非门加一个锁存器

图 1-3(b) 含 10K 个与非逻辑模块的 Plessey ERA60100 体系结构(© Plessey)

2. 中等颗粒度器件

这是最常见的 FPGA 结构(图 1-4(a))。代表性基本模块是小规模查找表(LUT,典型为 45 个输入、12 个输出),或由专用多路复用器(MPX)逻辑构成(如 Actel 的 ACT-2 器件)。LUT 的原理类似”真值表硬件化”:把逻辑函数的所有输入组合对应的输出预先存进小存储器,查表即得结果,因此任意组合逻辑都能实现。布线通道有短有长,带触发器的可编程 I/O 模块贴在器件边缘。

图 1-4(a) 中等颗粒度 FPGA 的体系结构:LUT 阵列 + 分层布线通道 + 边缘 I/O

3. 大颗粒度器件

代表是复杂可编程逻辑器件(CPLD),见图 1-4(b)。CPLD 可以理解为把许多简单可编程逻辑器件(SPLD)拼在一起,典型的 SPLD 就是图 1-5 所示的 GAL16V8 这类芯片:由可编程”与/或”阵列和通用 I/O 单元组成,通常有 810 个输入、34 个输出,支持约 20 个乘积项。SPLD 模块之间用宽总线互连(Altera 称之为可编程互连阵列 PIA),延时很短。宽总线加固定的 SPLD 时序,使 CPLD 的引脚到引脚延迟可以预先算准——这是 CPLD 相对 FPGA 的一个特色。

图 1-5(a) GAL16V8 的 8 个宏单元中的前 3 个(© Lattice)

图 1-5(b) GAL16V8 的输出逻辑宏单元(OLMC)内部结构(© Lattice)

1.2.2 按技术分类

FPL 几乎用到所有存储工艺:SRAM、EPROM、PROM、反熔丝和 Flash。初学者要抓住两个维度:能否重复编程是否易失

  • SRAM:FPGA 的主流技术,基于静态 CMOS 存储,可在系统上反复编程;但配置信息存在易失性存储器里,断电即丢,因此必须外挂一块”引导”器件在上电时加载配置。编程用单比特流,布线简单但耗时(毫秒级)。
  • EPROM:需紫外线擦除,多用于一次性编程。
  • PROM:电可擦写,可重复且在系统编程;配置信息不用下载,掉电不丢,还能更好地防止未授权复制。
  • Flash:基于 EPROM 的新技术,单元更小、“页方式”操作,可视为系统级可重编程的 PROM 等价物。
  • 反熔丝:一次性编程,不可逆,但抗辐射、可靠性高。

一句话总结各工艺的取舍:SRAM 灵活但易失,非易失工艺(EPROM/PROM/Flash/反熔丝)省掉了配置芯片但灵活性或密度各有短板;产品示例方面,Xilinx 的 Spartan、Altera 的 Cyclone 是 SRAM 型代表,Actel 的 ACT 系列用反熔丝。

1.2.3 FPL 的基准

怎么客观比较不同厂商的 FPL?Xilinx、Altera 和 Actel 共同建立了可编程电子产品性能协议(PREP)组织,制定了 9 种基准电路(表 1-3):数据通道、定时器/计数器、小型与大型状态机、算法电路(4 位×4 位无符号乘法器 + 8 位累加器)、16 位累加器、16 位计数器、16 位同步降值计数器、存储器映射器。测法是:让每个厂商用自家器件和工具,把同一个基准模块在指定器件里尽可能多地塞进去,同时尽量提高速度;塞进去的份数叫重复率(repetition rate)。对 DSP 而言,第 5、6 项(乘法器-累加器、累加器)最相关。

图 1-6 按相对频率给出 Altera 与 Xilinx 典型大学开发板器件的重复率。结论是:Xilinx 的速度看起来更高,Altera 的 FPGA 重复数更大;而无论如何,现代 FPGA 系列在 DSP 的复杂度和速度上都胜过 CPLD。关键原因是现代 FPGA 提供了快速进位逻辑(每比特传播延迟小于 0.1ns),多位宽快速加法不再需要昂贵的”超前进位”译码器。

图 1-6 Altera 与 Xilinx 系列 FPGA/CPLD 器件的 PREP 重复率相对频率比较

但 PREP 基准只回答”等效门数和速度”,做具体 DSP 设计时还要看这些属性:嵌入式阵列乘法器(18×18、18×25 等)、封装形式(BGA、TQFP、PGA)、DES/AES 数据流加密、嵌入式硬件微处理器(如 32 位 ARM Cortex-A9)、片上大块 RAM/ROM、片上快速 ADC、外部存储器支持(ZBT、DDR、QDR、SDRAM)、引脚间延迟、内部三态总线、读回/边界扫描、I/O 电压与压摆率可编程性、功耗,以及 ×1/×2/×4 总线标准的 IP 硬核等。

两家厂商的系列分工可以这么记:Xilinx 的 Virtex 主打性能、Kintex 面向 DSP 且价格较低、Artix 最便宜(接替 Spartan),另有内嵌 ARM 的 ZYNQ;多数器件配 18Kbit 或 36Kbit 存储块,第 6 代 Virtex 首次带 10 位 0.2MSPS 片上 ADC,第 7 代升级为 12 位 1MSPS 双通道 ADC,约 17 个片上传感器(温度、供电等)可供读取(图 1-7(b))。Altera 的 Stratix 性能最高、Arria 中等、Cyclone 成本/功耗/密度/性能都最低;逻辑模块从 4 输入 LUT 演进到最多 8 输入的 ALM(自适应逻辑模块,图 1-7(a)),使 3 输入加法器几乎能以 2 输入加法器的速度搭建——ALM 内含两个触发器、两个全加器、两个 4 输入 LUT、4 个 3 输入 LUT 和多个多路复用器。嵌入式乘法器规格从 9×9、18×18 到 27×27(第 5 代起把 3 个 9 位模块拼成一个快速 27×27 乘法器)。存储块覆盖 M512、M4K、M9K、M10K、M144K 等规格。注意软件限制:Quartus II 网络版 12.1 只有 Cyclone 系列可用,Arria/Stratix 需订购版;Xilinx 网络版只有 Spartan 可用。

图 1-7(a) Altera 的 ALM 模块:含 2 个触发器、2 个全加器和多个 LUT/多路复用器

图 1-7(b) Xilinx 第 7 代器件的高速片上 ADC 及传感器接入

功耗也是关键指标,移动应用尤其如此:CPLD 的”备用”功耗通常较高;FPGA 在高频应用下功耗会明显上升,1.4.2 节会给出具体的功耗分析示例。

1.3 DSP 的技术要求

从市场看(图 1-8),PLD 自 20 世纪 80 年代初问世以来曾长期保持约每年 20% 的增长,比 ASIC 高出 10 个百分点以上;2001 年微电子业整体衰退曾拖慢两者,2003 年起两大厂商又恢复约每年 10% 的增长。FPL 能胜过 ASIC,是因为它拿到了 ASIC 的大部分优点——尺寸、重量、功耗下降,吞吐量更高,可禁止未授权复制,器件与开发成本、电路板测试成本更低——同时避开了 ASIC 的两大痛点:

  • 开发时间缩短 3~4 倍(可快速做原型),且支持在线重复编程;
  • 小批量更省钱:少于 1000 片时 NRE 摊不薄,FPL 经济性碾压;真正的大批量(多于 1000 片)才轮到标准单元(CBIC)ASIC 出场——同样小片面积下它的门数是 FPL 的 10 倍以上。

中间地带的方案是”硬布线 FPGA”:把已验证的 FPGA 设计固化到门阵列上(Altera 叫 HardCopy ASIC,Xilinx 叫 EasyPath FPGA),兼顾性能与产量。

图 1-8 5 家主要厂商在 PLD/FPGA/CPLD 市场的收入走势

FPGA 和可编程信号处理器

PDSP 基于 RISC 思想,核心是”一个快速阵列乘法器(16×16 到 24×24 位定点或 32 位浮点)+ 加宽字长的累加器”,靠多级流水线让 MAC 速度只受乘法器限制。信号处理算法中 MAC 运算出现得极频繁,这正是 PDSP 的甜蜜点。那 FPGA 凭什么竞争?靠并行:一个 PDSP 芯片通常只有一两个 MAC 单元,而 FPGA 可以在一颗芯片上摆下多级 MAC 单元,在无线电、多媒体、卫星传输这类高带宽场合提供 PDSP 给不了的吞吐量;CORDIC、数论变换(NTT)、差错校正等算法上 FPL 也更有效率。业界的共识是:复杂算法(大量 if-then-else 分支)的应用归 PDSP,前端(传感器侧)的 FIR 滤波、CORDIC、FFT 归 FPGA——后者正是本书的主旨。

浮点性能方面,FPGA 也已给出有说服力的答卷。看表 1-6 的对比:任务是做尽可能多的千兆浮点乘-累加(GFPMACS)。一块 Stratix IV FPGA(DE4 开发板,大学项目价约 $1000,商业价 $3000)即可胜任;而最快的浮点 PDSP 是 TI 的 TMS320C6727B-350,主频 350MHz、提供 700MMAC(兆次乘加/秒)。需要多少片呢?代入数字:

即 62 片 PDSP 才追平单块 FPGA 的 43.5 GFPMACS(Stratix IV 单次浮点 MAC 约需 475 个 ALM 加 4 个嵌入式 9×9 乘法器)。成本上,62 片 × $29.03 ≈ $1799,而单块 FPGA 方案 $871——总成本改善超过 100%,电路板面积和功耗的节省更是显而易见。

1.4 设计实现

VLSI 设计可以在多个抽象层次进行(表 1-7):系统层讲性能说明,芯片层讲算法,寄存器层讲数据流(寄存器、ALU、计数器、多路复用器),门层讲布尔方程(与/或/异或/触发器),线路层讲微分方程(晶体管、R/L/C),最底层是几何版图。FPGA 的物理结构可编程但固定,不存在 ASIC 那样的全定制布局布线,因此在门/寄存器层次用寄存器转换级(RTL)硬件描述语言描述最能用足器件。现代 FPGA 复杂度激增,于是设计方法转向知识产权宏单元(IP 核/mega-core):IP 核提供预定义功能(如微处理器、UART),设计者只指定精度等参数,“合成器”自动生成最终代码或原理图。

FPGA 设计流程(图 1-9)按序走一遍:设计入口(图形或文本 HDL)→ 格式检查(语法错误、悬空导线等)→ 抽取功能网表,做 RTL 级功能仿真(用测试平台 testbench 提供激励数据),并生成 RTL 视图帮助理解电路结构。功能仿真不通过就回设计入口改。通过后进入设计实现(综合、布局布线,编译时间长得多),完成后布线确定,得到精确的资源占用数据,可执行带完整时序延迟信息的时序仿真和性能测试。全部达标就生成编程文件下载到开发板,否则回到设计入口。调试阶段还能借 JTAG 接口”透视”FPGA 内部:只读 I/O 单元叫边界扫描,能读回所有内部触发器叫完全扫描。选图形环境还是文本环境看个人:图形方式适合强调规则数据流的 DSP 算法,文本(HDL)方式更适合算法控制类设计,且能更精确地表达行为特性——本书采用文本方式。

图 1-9 CAD 设计周期:设计入口 → 功能仿真 → 设计实现 → 时序仿真 → 编程下载

例 1.1 三种 HDL 设计风格的比较(原书 VHDL 改写为 Verilog)

原书用 VHDL 演示了三种设计策略:结构化(组件实例化,类似图形网表)、数据流(并发语句)、行为/顺序(PROCESS 块)。下面的 Verilog 代码与原逻辑完全等价——模块名与端口语义保持一致,即:输入 8 位信号 abop1 和时钟 clk、异步复位 resetsum 输出 op1 + b 的寄存结果;c 输出 a + b(有符号整数);d 在每个时钟沿输出 s,而 s 逐拍累加 ab

// 原书 VHDL 改写为 Verilog:结构化 + 数据流 + 行为三种风格并存
module example #(
    parameter WIDTH = 8                          // 位宽(对应 GENERIC)
)(
    input  wire                clk,              // 系统时钟
    input  wire                reset,            // 异步复位
    input  wire [WIDTH-1:0]    a, b, op1,        // 逻辑向量输入
    output reg  [WIDTH-1:0]    sum,              // 逻辑向量输出
    output reg  signed [7:0]   c, d              // 整数输出(对应 S8: -128~127)
);
 
    // ---------- 1. 结构化风格:实例化库元件(对应 COMPONENT) ----------
    wire [WIDTH-1:0] op2 = b;                    // 类型转换
    wire [WIDTH-1:0] op3;
 
    lib_add_sub #(.WIDTH(WIDTH)) add1 (          // 加法器
        .dataa (op1),
        .datab (op2),
        .result(op3)
    );
    lib_ff #(.WIDTH(WIDTH)) reg1 (               // 寄存器
        .clock (clk),
        .data  (op3),
        .q     (sum)
    );
 
    // ---------- 2. 数据流风格:并发赋值(语句顺序无关紧要) ----------
    wire signed [7:0] a_i = a;                   // 有符号类型转换
    wire signed [7:0] b_i = b;
    assign c = a_i + b_i;
 
    // ---------- 3. 行为/顺序风格:PROCESS 对应 always 块 ----------
    reg signed [7:0] s;
    always @(posedge clk or posedge reset) begin
        if (reset) begin                         // 异步清零
            s <= 8'sd0;
            d <= 8'sd0;
        end else begin
            s = s + a_i;      // 阻塞赋值:立即生效,等价于 VHDL 的 VARIABLE
            s = s + b_i;      // 这两条语句的顺序会改变结果!
            d <= s;           // 非阻塞赋值:d 得到两次累加之后的 s
        end
    end
endmodule
 
// 被实例化的两个简单元件(实际设计通常由综合器自动推断)
module lib_add_sub #(parameter WIDTH=8)
   (input  wire [WIDTH-1:0] dataa, datab,
    output wire [WIDTH-1:0] result);
    assign result = dataa + datab;               // 简化模型:仅演示加法
endmodule
 
module lib_ff #(parameter WIDTH=8)
   (input  wire                clock,
    input  wire [WIDTH-1:0]    data,
    output reg  [WIDTH-1:0]    q);
    always @(posedge clock) q <= data;
endmodule

初学者最容易在这里犯糊涂的是 VHDL”信号 vs 变量”的区别,对应到 Verilog 就是”非阻塞 vs 阻塞赋值”。Verilog 中 assignwire 类似 VHDL 的并发 SIGNAL 语句——语句先后顺序无所谓c = a_i + b_ia_ib_i 的定义谁前谁后都不影响结果。而在 always(相当于 PROCESS)内部:阻塞赋值(=)像普通 C 程序一样逐条立即生效,所以 s 先加 a_i、再加 b_id 最后拿到的是两次累加后s;如果写成非阻塞赋值(<=),三条语句同时取”旧值”,d 就拿不到 b_i 那一步的结果。这正复现了原书 VHDL 注释强调的顺序陷阱。另外注意类型:VHDL 是强类型语言,INTEGER 与 STD_LOGIC_VECTOR 之间必须显式调用转换函数;Verilog 里用 signed 修饰即可直接做有符号运算。

代码编译、仿真通过后,最终得到编程文件下载到原型开发板。Altera 的 DE2-115 开发板(图 1-10(a),基于 Cyclone IV)是教学性价比之选:双通道音频 CODEC、大容量外部存储,以及 USB、VGA、PS/2、以太网、7 段 LED、LCD、开关、按键等丰富外设。Xilinx 几乎不直接卖板子,第三方板很便宜,如 Digilent 的 Atlys(图 1-10(b),学术价 $199):Spartan-6 XC6SLX45 芯片、16MB 闪存、128MB 内存、8 个 LED、8 个开关、5 个按钮,音频 AC-97 CODEC 和 4 个 HDMI 端口适合 DSP 与视频实验。

图 1-10(a) Altera DE2-115 开发板(Cyclone IV 系列)

图 1-10(b) Xilinx Atlys 开发板:带 ADC/DAC 音频编码解码器

1.4.1 FPGA 的结构

21 世纪初的 FPGA 已具备实现 DSP 的关键硬件:快速进位逻辑(32 位非流水线加法器可跑 300MHz 以上)、嵌入式 18×18 乘法器和大容量存储器。两大厂商的脉络:Xilinx 由 XC4000 派生出 Spartan(低成本)和 Virtex(高性能);Altera 由 FLEX 10K 派生出 Stratix(高性能)和 Cyclone(低成本)。布线哲学不同:Xilinx 采用 FPGA 典型的分层布线,Altera 则沿袭 CPLD 的宽总线结构——但两者基本模块都已是 FPGA 式的中等颗粒度 LUT,而不是 CPLD 的大 PLA。Altera 把 LUT 称为逻辑元件(LE),若干 LE 组成逻辑阵列模块(LAB),每个 LAB 的 LE 数随代际增多:FLEX10K 每 LAB 8 个 LE,APEX20K 10 个,Cyclone II 16 个。

以 Xilinx Spartan-6 的 XC6SLX45(Atlys 板上的芯片)为例。它的基本单元叫切片(slice),分 M、L、X 三型(图 1-11):两个切片组成一个可配置逻辑模块(CLB),共 8 个 6输入LUT(或 16 个 5输入LUT)和 16 个触发器。三型的差别要记牢:约 25% 是 M 型,功能全(LUT 可当 64×1 的 RAM/ROM 用、支持 256 位分布式 RAM、128 位移位寄存器);25% 是 L 型,没有存储/移位寄存器功能;50% 是 X 型,连移位寄存器、算术进位和宽多路复用都没有。为什么这样”缺斤短两”?——省面积省成本,综合工具会按需分配。Spartan-6 还有大块存储器(每块 18432 位,不含奇偶校验位时 16384 位),可配成 、…、:地址位每加一位,数据位宽减半,总量不变。DSP 亮点是嵌入式快速 18×18 位有符号阵列乘法器(也可做 17×17 无符号),以及 4 个完整时钟网络(DCM),让同一芯片里多个时钟域低偏斜并行运转。

图 1-11 Spartan-6 的 1/4切片:X 型只有 LUT 与两个触发器,L 型增加快速进位逻辑,M 型具备全部特征(© Xilinx)

再看 Altera Cyclone IV 的 EP4CE115(DE2-115 板上的芯片)。其基本模块是逻辑元件(LE):一个 4 输入 1 输出 LUT(或一个 3 输入 LUT)加一个触发器,外加快速进位或”与/非”乘积项扩展电路(图 1-12)。LE 有两种模式:正常模式下当 4 输入 LUT;算法(算术)模式下当带快速进位链的 3 输入 LUT——这正是快速加法器的基础。16 个 LE 组成一个 LAB;每行至少配一个 18×18 乘法器和一个 M9K 存储块。18×18 乘法器可拆成两个 9×9 有符号乘法器用;M9K 可配置成 、…、 的 RAM/ROM,每字节还附 1 位奇偶校验(如 256×36 配置)用于数据完整性检查。M9K 和 LAB 之间用高速宽总线连接(图 1-13),片内多个 PLL 产生低偏斜的多个时钟域。

图 1-12 Cyclone IV 的逻辑单元(LE):LUT + 触发器 + 快速进位链(© Altera)

两种布线策略各有道理,值得对比记忆:Xilinx 多用局部布线、少用全局布线——这恰好契合 DSP,因为信号处理算法的数据大多是”局部流动”的(乘累加就在相邻单元间发生);Altera 的宽总线也有价值,因为 DSP 的典型操作不是逐位的”位切片”,而是把 16~32 位宽的数据矢量整体搬到下一个 DSP 模块。

1.4.2 Altera EP4CE115F29C7

本书全程使用的器件是 DE2-115 开发板上的 EP4CE115F29C7,属于 Cyclone IV E 系列。型号名不是乱码,逐段拆解:

图 1-14 EP4CE115F29C7 器件命名法逐段解读(EP4 = Cyclone IV E;115 = 约 115K 逻辑单元;F29 = 29mm FBGA 封装;C7 = 商用级速度等级 7)(© Altera)

配套软件是 Altera 的 Quartus II(可从 www.altera.com 免费下载 Web 版),集成 HDL 编辑器、定时估算和位流发生器;Web 版的局限是部分器件引脚选项受限。本书所有示例在 VHDL 和 Verilog 下都可行,因此用 Xilinx ISE 编译器加 ISIM 仿真器同样能编译,其他版本的 Quartus II 可用配套的 qvhdl.tcl / qv.tcl 脚本一键编译所有实例。

1. 逻辑资源

EP4CE115 的逻辑密度约 115000 个 LE,表 1-9 给出精确数字:114480 个 LE——这也是它最多能实现的全加器数目。由这些数字可以算出几个有用的量:

  • LAB 数量:16 个 LE 组成一个 LAB,故 个 LAB。注意它不等于行列乘积——器件左侧中部有 JTAG 接口区占用了 个 LAB 的位置,所以总布局不是规则矩形。
  • M9K 存储块数量:器件含 6 列 M9K 存储器,列宽恰为一个 LAB,每列 72 块,故总数为 块。换算成总容量: 的片上存储。
  • 嵌入式乘法器:266 个 18×18 位快速阵列乘法器;若按 9×9 使用,数量翻倍到 532 个(图 1-15)。对 DSP 来说这 266 个乘法器才是”硬通货”——它们让 266 路乘-累加可以真正并行。

2. 额外的资源和路由

数据表里不再像 FLEX10K 时代那样明确列出水平/垂直总线数目,但可以从 Quartus 的 Compiler Report(Fitter → Resource Section → Logic and Routing Section)读出整体路由资源(表 1-10)。路由分层从近到远:局部互连与直接链路(一个 LE 可驱动多达 48 个 LE)→ R4/C4 快速行列连接(到达 ±4 个 LAB 范围,或 3 个 LAB 加一个乘法器/M9K)→ R24/C16 长线(分别跨 24 行、16 列 LAB,接近横跨整片),源与目的 LAB 不同行又不同列时可组合任意行列连接;另有覆盖全片的全局时钟网络。EP4CE115 有 4 个 PLL、20 个时钟网络保证短时钟偏移;每个 PLL 有 5 个输出计数器,可产生不同频率或相位偏移——例如 DE2 板的 DRAM 需要 0 和 −3ns 的时钟偏移,就由一个 PLL 直接生成。

控制信号方面要注意一个限制:同一 LAB 内 16 个 LE 共享相同的同步清零/置载信号,以及两个异步清零、两个时钟、两个使能。DSP 设计常用宽总线,一个 LAB 往往装不下一条完整总线,控制信号的自由度因此受限,这是综合工具布局时必须权衡的。LAB 本地互联可由行/列总线或本 LAB 的 LE 驱动,邻近的 LAB、PLL、存储块、乘法器也能驱动它。不同层级连接的延迟差异很大,综合工具会尽量把关联逻辑摆放得彼此靠近以压低互连延迟——例如一个 32 位加法器最好落在上下相邻两行的两个 LAB 里。

图 1-13 Cyclone IV 逻辑阵列模块(LAB)及其路由资源(© Altera)

图 1-15 嵌入式乘法器的体系结构:一个 18×18 乘法器可拆分为两个 9×9(© Altera)

3. 定时估算

早期的 Quartus II 与 ISE 只有两个优化目标:面积速度。但如今器件密度大增、片上系统带多时钟域、I/O 有多种时钟模式还有分频器,单纯”面积或速度”的二选一不再够用。Altera 改用基于概要设计约束(SDC)文件的时序规范,更接近 ASIC 的设计风格。流程是:综合工具先满足时序约束,再优化面积。同一个电路,工具可以选用不同库元素(脉动进位、进位保存、超前进位加法器等)来达标。一个实用技巧是”过约束/欠约束”:若不给 SDC 文件而假定 1GHz 的目标性能,设计会被过度约束——工具会拼命提速;若只想压面积,就设一个较低的目标时钟。本书的设计示例只关心速度,用默认设置(无 SDC 文件)按 1GHz 过约束运行即可;编译报告里会报”定时未满足”的警告,这是可预见的,不必理会。动手之前对设计规模做大致评估、再决定优化方向,是值得养成的习惯。

例 1.2:一个 32 位加法器到底能跑多快

为什么要在动手之前先估算速度

在 FPGA 里做数字信号处理,最先碰到的现实问题往往不是”功能对不对”,而是”速度够不够”。加法器是几乎所有 DSP 电路的基本零件,所以教材用”32 位加法器”这个最简单的例子,示范如何在写代码之前就粗略估计出电路的最高工作频率。学会这种”纸面估算”,你就能在设计初期判断方案是否可行,而不用等到编译完才发现跑不到目标频率。

从进位链说起:理想情况的上限

FPGA 里的加法器不是拿零散的逻辑门拼出来的。以 Altera Cyclone IV 为例,一个 LAB(Logic Array Block,逻辑阵列块,就是一小组排在一起的逻辑单元)内部有一条”快速进位链”——一条专供进位信号高速横向传递的硬连线。32 位加法器大约占用两个 LAB,进位信号要从一个 LAB 串到另一个 LAB。

进位链上”进位输入到进位输出”这一段的延迟,对 Cyclone IV 第 7 速度等级(C7)的芯片约为 66ps(皮秒,1ps = 10⁻¹² 秒)。如果只看这段,理论上限是:

这是”只算进位链”的理想值。实际电路里,数据从寄存器出发、穿过加法器、再被下一个寄存器捕获,途中还有好几段额外延迟,任何一段都不能省。

把整条路径的延迟一段段加起来

一次完整的”寄存器到寄存器”加法,要经过这些环节:

  1. (寄存器时钟到输出延迟)= 232ps:时钟沿到来后,源寄存器把数据送出来所需的时间。
  2. (互连延迟)= 501ps:数据从源寄存器经过布线到达第一个全加器的时间。
  3. (第一次进位生成)= 414ps:最低位产生第一个进位所需的时间——它比后续的 66ps 慢,因为要经过 LUT 计算而不是走专用进位链。
  4. 中间 30 位的进位传递 :注意是 30 次而不是 32 次,因为第一位由 负责。
  5. (末位求和)= 536ps:最高位的进位到达后,还要通过 LUT 算出完整的和位。
  6. (目标寄存器建立时间)= 87ps:数据必须在下一个时钟沿之前提前这么久稳定下来,才能被可靠捕获。

再考虑可能出现的时钟偏移(clock skew,即时钟信号到达不同寄存器的时间差异),额外加 82ps,总延迟估算为:

资源方面,加法器本身大约需要 32 个 LE(Logic Element,逻辑单元,FPGA 最基本的可编程单元);如果要把输入数据先寄存起来,还要额外 个 LE(请参阅练习 1.7)。

用 TimeQuest 看时序裕量:红色与绿色

以上是手算。工具会算得更细:图 1-15 就是 Quartus II 的 TimeQuest Analyzer 显示的完整时序路径(含时钟延迟)。因为我们没有指定时钟周期,TimeQuest 默认按 1ns(即 1GHz)约束——对加法器来说这是”过度约束”,实际路径跑不到这么快,于是时序裕量(Slack,即允许时间减去实际时间的余量)为负,报告里以红色显示为违规,见图 1-15(a) 第 7 行。这并不代表设计失败,只说明”1GHz 达不到”。要满足时序,通常就用默认的 1ns 再加上绝对值为负的松弛来判断。如果我们用 Synopsys Design Constraints(SDC)文件把时钟周期明确设为 5ns(即 200MHz),重新分析就会发现裕量为正,以绿色显示,见图 1-15(b)。


(a) 负时序裕量显示时序违规


(b) 正时序裕量表明满足时序要求

布局与 I/O 带来的额外代价

上面的估算还有两个前提:两个 LAB 恰好放在同一列相邻位置、数据来自芯片内部的寄存器。如果布局工具把两个 LAB 放得相距较远,或者信号直接来自 I/O 引脚,延迟都会明显变大。教材给了一个直观对比:在 DE2-115 开发板上,从拨码开关 SW[1] 输入、直接连到数码管引脚 HEX[1] 输出的纯 I/O 传输延迟就有 11.3ns——远大于加法器本身约 3.9ns 的路径。数据手册报告的最佳性能,都是把 I/O 数据寄存器尽量靠近设计单元时测得的。

另外要注意:乘法器和 M9K 存储模块(但不是加法器)内部带有额外的 I/O 寄存器来实现最高速度(见图 1-14)。做资源估算时这些寄存器通常不计入,因为一般假定前级单元已经对数据用了输出寄存器;但情况并非总是如此,所以教材把加法器设计所需的额外寄存器写在圆括号里。表 1-11 汇总了典型测量结果。与数据手册对比会发现,TimeQuest 给出的上限频率比手册值略低——这是一种保守且更安全的估算,实际电路往往能跑得稍快一些。

表 1-11 Cyclone IV C7 的部分典型时序电路性能 Fmax 和资源数据

设计LEM9K 存储器嵌入式乘法器 9×9时序电路性能 (MHz)
16 位加法器16 (+32)363
32 位加法器32 (+64)257
64 位加法器64 (+128)169
ROM 471274
RAM 1274
9 位×9 位乘法器1300
18 位×18 位乘法器2300

从表中还能看出一个规律:加法器位宽翻倍,Fmax 大约按比例下降——因为进位链是串行的,位越宽,进位要”跑”的步数越多。

功耗:FPGA 设计的第二道门槛

静态功耗与动态功耗

对移动设备、电池供电的系统来说,功耗往往比速度更关键。FPGA 的总功耗由两部分组成:

  1. 静态功耗:芯片通电但不做任何切换时消耗的功率。CMOS 工艺的待机功耗很小,对 EP4CE115F29C7 而言 ,它与你的设计内容基本无关。
  2. 动态功耗:信号翻转(从 0 变 1、从 1 变 0)时对电容充放电消耗的功率,来自逻辑、乘法器、RAM、PLL、时钟网络和 I/O,记为 相关项。它主要与时钟频率和所用资源数量成正比——电路动得越快、越多,功耗越大。

Cyclone IV 由 TSMC 采用 2.5V、60nm 低 k 电介质工艺制造,支持 3.3V、2.5V、1.8V、1.5V、1.2V 多种 I/O 电压,因此推荐使用 3.3V 或更低电压级别的器件以利于低功耗设计。

Altera 提供了一个 Excel 工具 PowerPlay Early Power Estimator,在项目早期就能估算功耗(比如推算电池寿命)和散热需求。

用经验公式估算 LE 的动态功耗

对逻辑单元,可以用下面的经验公式:

其中: 是常数; 是以 MHz 计的工作频率; 是器件中使用的逻辑单元总数; 是逻辑单元在每个时钟周期内发生翻转的平均百分比(典型值 12.5%——不是所有触发器每个周期都翻转)。

把 EP4CE115F29C7 的所有资源按 100MHz 时钟、典型翻转率”全部用满”算一遍,就得到表 1-12。

表 1-12 Cyclone IV EP4CE115F29C7 的功耗估算

参数单元触发百分比 (%)功耗 (mW)
159
LE 114 480 @ 100MHz114 48012.5%1170
M9K 模块存储器43250%74
9 位×9 位乘法器53212.5%153
I/O 单元 (2.5V, 4mA)52812.5%164
PLL432
时钟网络115 706486
总计2238

这个表也告诉我们:如果实际设计用不到全部资源、时钟也没那么高,就要按比例调整公式 (1-1)。例如系统时钟从 100MHz 降到 10MHz,动态功耗降低为原来的 1/10:

此时静态功耗 159mW 占总功耗的比例高达 ——低频系统里”躺着不动”的开销反而成了大头。

更精确的分析:让仿真数据说话

Early Power Estimator 的短板在于:翻转率是你”拍脑袋”填的。以图 1-16 的频率合成器为例,RAM 模块按 50% 翻转率估可能没问题,但累加器各 bit 的翻转率差别极大——最低位(LSB)几乎每个时钟都翻转,最高位(MSB)很久才翻一次(累加器输出的是三角波)。要更准,就用 Quartus 的 Processing 菜单里的 PowerPlay Power Analyzer Tool:它可以直接读取仿真输出的翻转数据。仿真器会生成 “Signal Activity File” 或 “Value Change Dump”(VCD)文件作为分析器输入。选项从粗到细依次是:无仿真时通过 TimeQuest SDC 指定时钟频率并设默认翻转率;用功能仿真或 RTL 仿真数据(只需完整编译,更准);最准的是用 ModelSim 的门级时序仿真数据(包含精确的 LE 翻转、毛刺、总线驱动信息)。

表 1-13 对比了同一个设计(50MHz、12.5% 翻转率假设)在估算器与三种分析模式下的结果:估算与实际分析之间约有 10% 的偏差。结论是——项目早期就可以先估算,后期用仿真数据精化。

表 1-13 图 1-16 设计的功耗对比(PPEPE = Early Power Estimator,PPPA = PowerPlay Power Analyzer)

PPEPE 估算器PPPA 估算器PPPA RTL 仿真PPPA 定时
所需 VCD
参数功率/mW功率/mW功率/mW功率/mW
静态13598.498.498.5
动态22.32.63.7
I/O438.938.950.4
总计141139.6139.9152.6

1.4.3 案例研究:频率合成器

设计目标与整体思路

从这一节开始,教材用一个贯穿全书前几章的案例研究来演示完整的 FPGA 开发流程:基于 Philips PM5190(约 1979 年的老式仪器,见图 1-16)的模式,实现一个经典的频率合成器(function generator)。其核心结构非常简单:

  • 一个 32 位累加器:每个时钟周期把增量 M 加到累加器上;
  • 取累加器的高 8 位 MSB(Most Significant Bit,最高有效位)作为地址,查一张 SIN-ROM 查找表(LUT,Look-Up Table),输出对应的正弦值。

为什么这样能得到正弦波?累加器不断加 M,高 8 位就周期性地从 0 数到 255 再归零,相当于一个”相位”匀速前进;M 越大,相位走得越快,输出频率越高。ROM 里预先存好了 256 个正弦值,相位一到就查出对应幅度——这就是最朴素的 DDS(直接数字频率合成)思想。


图 1-16 PM5190 频率合成器

教材用行为级 HDL 代码实现(fun_text.vhd / fun_text.v),不实例化 LPM 组件,要求同样的代码能被 Altera Quartus II 与 Xilinx ISE 综合,并在 ModelSim / ISIM 上完成 RTL 与时序仿真。完整的开发流程分四步:(1) 编译;(2) 平面布置图;(3) 仿真;(4) 性能评估。

第一步:设计的编译

启动 Quartus II,用 File | Open Project 或 File | New Project Wizard 新建项目:指定项目目录,项目名和顶层设计名都叫 fun_text;Next 后添加 HDL 文件 fun_text.v;再 Next,从 Cyclone IV 系列选 EP4CE115F29C7;仿真工具选 MODELSIM-ALTERA,Finish。如果使用配套资料里的 fun_text.qsf 项目文件,这些设置已经就绪。

原书 VHDL 改写为 Verilog,累加器加 ROM 的顶层设计如下(可综合风格):

// A 32-bit function generator using accumulator and ROM
module fun_text #(
    parameter WIDTH = 32                    // 位宽
)(
    input  wire             clk,            // 系统时钟
    input  wire             reset,          // 异步复位(高电平有效)
    input  wire [WIDTH-1:0] M,              // 累加器增量
    output wire [7:0]       acc,            // 累加器 MSB 输出
    output wire [7:0]       sin             // 正弦输出
);
 
    reg  [WIDTH-1:0] acc32;                 // 32 位累加寄存器
    wire [7:0]       msbs;                  // 高 8 位
 
    // 32 位累加器:加法器后接寄存器,异步高电平有效复位
    always @(posedge clk or posedge reset) begin
        if (reset)
            acc32 <= {WIDTH{1'b0}};         // 复位清零
        else
            acc32 <= acc32 + M;             // 每个时钟沿加上 M 并存入寄存器
    end
 
    assign msbs = acc32[31:24];             // 截取最高 8 位作为 ROM 地址
    assign acc  = msbs;
 
    // 实例化 ROM 查找表
    sine256x8 ROM (
        .clk (clk),
        .addr(msbs),
        .data(sin)
    );
 
endmodule

逐段解读:Verilog 的 module 端口列表对应原 VHDL 的 ENTITY,parameter 对应 GENERIC(可在实例化时改位宽)。always @(posedge clk or posedge reset) 实现带异步复位的寄存器,这是可综合代码的标准写法。assign 语句完成连线:把累加器高 8 位接成 ROM 地址并同时引到输出端口。最后像 VHDL 的 COMPONENT 实例化一样,把 ROM 组件接进电路。

配套的 ROM 表文件是 sine256x8(Verilog 数据文件为 sine256x8.txt)。要查看它,可在 Project Navigator 窗口(左上)双击打开。带初始数据的综合 ROM/RAM 并不好写:VHDL 有综合子集标准 VHDL 1076.6-2004,但更省事的办法是用工具自带的语法模板(Altera:Edit→Insert Template→VHDL→Full Designs→RAMs and ROMs→Dual-Port ROM;Xilinx:Edit→Insert Template→VHDL→Synthesis Constructs→Coding Examples→ROM→Example Code)。Altera 推荐用函数调用初始化,Xilinx 用 CONSTANT 数组初始化——后者在功能与时序仿真中表现一致,成为本书首选。1076.6-2004 定义的综合属性目前尚无厂商支持。而在 Verilog 中,ROM/RAM 初始化已写入语言参考手册(LRM),最可靠的方法是用 $readmemh() 配合 initial 语句从文本文件加载内容。

编译前做两件设置:一是 Assignment→Settings→Analysis & Synthesis Settings 中把 Optimization Technique 选为 Speed(速度优化);二是用 SDC 文件设置时序约束,默认 1ns 在 Speed 优化下通常够用。然后点 Processing 菜单的编译器工具(右箭头)。HDL 窗口左侧显示编译进度,包含全部步骤:Analysis & Synthesis(分析与综合)、Fitter(布局布线)、Assembler(装配)、Timing Analysis(时序分析)、Netlist Writer(网表输出)、Program Device(编程下载)。也可以单独点 Processing→Start→Start Analysis & Synthesis(快捷键 Ctrl+K)先做语法检查并生成资源估算报告;语法通过后再按编译器窗口左下角 Start(快捷键 Ctrl+L)做完整编译。全部通过后,Compilation Report 显示本设计用了 32 个 LE 和 2048 个存储器位——正好对应 256×8 的正弦表。存储器初始化文件由配套资料 util 目录下的 sine.exe 程序生成。图 1-17(b) 总结了 Quartus II 编译器窗口中的全部处理步骤。


(a) 项目导航器 (b) Quartus II 中的编译步骤 图 1-17 处理步骤

用 RTL 查看器做图形验证

写完代码想”眼见为实”地确认电路结构,可以用 Quartus II 的 RTL 查看器(Tools→Netlist Viewers→RTL Viewer)。fun_text 的电路如图 1-18 所示:能看到累加寄存器、高 8 位截取和 ROM 实例。另有一个 Technology Map Viewer 显示电路如何精确映射到 FPGA 底层资源,但即便这么小的设计,其细节也过于繁杂,不适合设计研究阶段使用。


图 1-18 频率合成器的 RTL 视图

第二步:平面布置图

要看电路在芯片上”住”在哪里,点第 6 个按钮(Chip Planner)或 Tool | Chip Planner 命令,打开 Chip Editor 视图,见图 1-19。用 Zoom in(放大镜)放大后:累加器占用的两个 LAB 以蓝色高亮,M9K 模块以绿色高亮,若干 I/O 单元以褐色高亮。点左侧的 Bird’s Eye View(鸟瞰图)按钮,Chip Editor 会用蓝色线条标出从累加器最低位到最高位的最长路径——这正是例 1.2 里决定 Fmax 的那条进位链。选中 M4K/M9K 模块后,反复点击 Generate Fan-In / Fan-Out Connections 按钮,可以逐层展开它的扇入、扇出连接,观察布线资源的使用情况。


图 1-19 频率合成器设计的平面布置图

第三步:仿真

两大 FPGA 厂商在仿真器策略上走了相反的路:Altera 过去用内置 VWF 模拟器(到 Quartus II 9.1 为止),后来推荐外部的 ModelSim-Altera 或 Qsim;Xilinx 从 12.3 版(2010 年底)起停发免费 ModelSim,改为 ISE 内集成的免费 ISIM。

仿真激励有两种给法:TCL/DO 脚本,或用 HDL 写测试平台(testbench——一小段 HDL,实例化被测电路并施加激励)。用测试平台时,时钟可以这样生成(原书 VHDL 语句改写为 Verilog):

always #5 clk = ~clk;   // 每 5ns翻转一次,得到 2×5ns = 10ns 的时钟周期

但 Xilinx 的时序仿真网表(*_times.vhd 一类)由功能网表直接综合而来,全部采用 STD_LOGIC 类型,原来的 ENTITY 数据类型和 GENERIC 参数都被丢弃。想用同一个 VHDL 测试平台跑 RTL 和时序仿真,实体就只能限制为单一数据类型——不能用整数、有符号/浮点类型、缓冲模式和泛型参数,这严重妨碍代码重用,往往得为 RTL 和时序各写一个测试平台。而直接用 TCL 激励脚本就没有这个问题:同一份脚本 RTL、时序仿真通用,甚至 VHDL 和 Verilog 设计也能共用同一激励文件,只是编译顺序不同;ISIM 的 TCL 脚本与 ModelSim 的 DO 文件风格也非常接近,方便在两个工具间过渡。

Quartus II 有两个免费仿真选择:MODELSIM-Altera(专业级);以及更易上手的 Qsim(不用写测试平台/脚本即可分配 I/O,但功能较少,且 12.1 版不支持 Cyclone IV)。因此教材选 MODELSIM-Altera 为默认模拟器。

文件名约定:* 代表项目名。RTL 仿真用 *.vhd / *.v;时序仿真用 Altera 的 *.vho / *.vo 或 Xilinx 的 *_timesim.vhd / *_timesim.v;RTL 与时序仿真共用同一激励文件 *.do(Altera)/ *.tcl(Xilinx)。

打开 ModelSim-Altera,用 File→Change Directory 切到 HDL 与脚本所在目录,用 dir *.dodir *.vhd 确认文件在位。RTL 仿真运行 do fun_text.do 0,时序仿真先在 Quartus/ISE 完成编译,再运行 do fun_text.do 1。功能仿真结果见图 1-20。这个例子的 “do” 脚本如下:

set project_name "fun_text"
do tb_ini.do $1 sine256x8
 
####### 把 I/O 信号加入波形窗口
add wave -divider "Inputs:"
add wave reset clk
radix -unsigned
add wave M
add wave -divider "Outputs:"
add wave acc sin
add wave -divider -height 80 {Analog sine:}
add wave -color Red -format Analog-Step \
-radix unsigned -scale 0.25 sin
 
####### 施加激励
force clk 0 0 ns, 1 5 ns -r 10 ns
force reset 1 0 ns, 0 10 ns
force M 214748365 0 ns
 
####### 运行仿真
run 250 ns
wave zoomfull
configure wave -gridperiod 5ns
configure wave -timelineunits ns

脚本包含 4 个典型部分:(1) 定义项目名,由 tb_ini.do 带参数(0=RTL,1=时序)编译各组成文件,并为时序仿真补充局部信号;(2) 按先输入后输出的顺序把信号加入波形窗口,用分隔线分组,最后一条专门定义了正弦信号的”模拟量”(Analog-Step)显示方式;(3) 施加激励——周期信号 clk 与非周期信号 reset、M;(4) 运行仿真并设置显示比例、网格与时间单位。

看仿真参数怎么定的:时钟周期取 ;增量 ,所以输出正弦的一个周期恰好占 20 个时钟周期,即输出频率 。注意 ROM 用二进制偏移量编码(0 电平对应码值 128),这是 D/A 转换器的典型做法。这些表数据由一小段 MATLAB 脚本或 C 程序生成;配套资料的 sine.exe 可同时生成 VHDL 和 Verilog 用的表格,支持十六进制(默认,便于阅读)、二进制或八进制。

第四步:性能分析

时序数据要在完整编译后才能得到。教材默认在 QSF 文件里写 FMAX_REQUIREMENT = 1ns(相当于 1GHz):我们的设计多半跑不到这么快,但”过度约束”能逼迫编译器朝着最快速度去综合。编译时会出现两个可忽略的提示:警告——找不到 Synopsys 约束文件 fun_text.sdc;严重警告——定时不满足要求。

TimeQuest 的结果见图 1-21 的编译报告,包含 Slow 85C、Slow 0C、Fast 0C 三种工艺-电压-温度模型的时序数据;我们取最悲观的 Slow 85C。Fmax Summary 给出电路可运行的最大频率;有时性能还受 I/O 引脚最高 250MHz 的限制。若设计是纯组合逻辑(没有寄存器到寄存器的路径),Fmax 一栏会显示”没有路径可报告”。


图 1-21 来自 TimeQuest 时序分析器的频率合成器寄存器性能

Xilinx 侧更简单:只需在实现视图中右键 Synthesizer-XST,把综合优化目标设为 Speed;完整编译后查看 Post-PAR 静态时序报告,点击目标时钟 CLK 即可在报告末尾看到最大时钟频率。

至此,频率合成器的案例研究完成。

1.4.4 用知识产权内核进行设计

为什么要用 IP 内核

FPGA 以快速原型设计著称,但前提是所需的 HDL 模块已经写好且测试充分。复杂模块——PCI 总线接口、流水线 FFT、FIR 滤波器、微处理器——动辄要开发数周甚至数月。想根本性地缩短开发时间,可以购买现成的知识产权(Intellectual Property, IP)内核:预先开发好的大规模模块。标准模块(数控振荡器 NCO、FIR 滤波器、FFT、微处理器)可从 FPGA 供应商直接获得;更专业的模块(AES/DES/JPEG 编解码器、浮点库、I2C、以太网接口)来自第三方供应商。Quartus II 里一些模块免费,更大更复杂的则价格不菲——但只要能满足需求,用现成 IP 通常比自己造轮子更经济。

IP 内核有三种主要形态,各自在设计灵活性与结果可靠性之间做了不同的取舍。

1. 软内核

软内核是对组件的行为级描述,需要用 FPGA 供应商的工具综合。通常以 HDL(VHDL 或 Verilog)源码交付,用户可以修改,甚至能在综合前针对特定器件增删功能。代价是:要达到预期的规模、速度和功耗,底层还有不少工作要做。供应商很少以这种形式提供模块(如 Altera 的 Nios 与 Xilinx 的 PicoBlaze 微处理器)。它的缺点也很清楚:以可综合 HDL 交付,竞争对手很容易把内核用于其他 FPGA 工具链甚至基于单元的 ASIC,知识产权难以保护,因此第三方 HDL 形式的模块价格通常远高于下面要讲的参数化内核。

2. 参数化内核

参数化内核是对组件的结构化说明:设计参数可以在生成/综合前调整,但 HDL 源码不开放。Altera 和 Xilinx 提供的大多数内核属于这一类,如 NCO、FIR 滤波器编译器、FFT(并行/串行)和 Nios II 等嵌入式处理器。它灵活易用,同时因为其他厂商无法使用,知识产权保护比软内核好得多。另一个突出优点:资源(LE、乘法器、块 RAM)占用预测误差通常只有百分之几,综合之前就能快速评估规模、速度与功耗;配套的 HDL 测试平台提供周期精确建模,C 和 MATLAB 也有行为精确建模标准。生成代码通常只需几秒钟。本节末尾及后续章节(第 3 章 FIR、第 6 章 FFT)会继续研究 NCO 参数化内核。

3. 硬内核

硬内核(固定网表内核)是物理级描述,以 EDIF 等物理布局格式交付,通常针对具体器件(系列)优化,用于有严格实时约束的场合(如 PCI 总线接口)。设计参数固定(例如 16 位 256 点 FFT),但行为级说明仍可用于更大项目中的仿真和集成。大多数第三方内核和 Xilinx 的部分免费 FFT 内核属于此类。因为布局固定,时序与资源数据是精确的,不依赖综合结果;但底层参数不可改——如果你的 FFT 需要 12 位或 24 位输入,16 位 256 点的硬内核就用不上。

4. 三种内核的比较与两大难题

比较三类 IP 模块,本质上是在**设计灵活性(软内核)快速出结果、数据可靠性(硬内核)**之间抉择。软内核灵活(改参数、换器件/工艺都容易),但调试耗时;硬内核已在硅片上验证过,能缩短开发、测试、调试时间,却看不到源码。参数化内核通常是灵活性与可靠性之间的最佳折中。

当前 IP 技术还有两大难题:定价知识产权保护。内核可重复使用,定价就依赖客户使用 IP 的次数——这与专利授权面临的问题类似,往往需要长期许可协议,滥用要付高额罚金。FPGA 供应商自家的参数化模块定价合理,因为客户大量使用 IP 就得多买该供应商的器件,供应商有”第二次收益”;第三方 IP 供应商没有这层收益,授权协议(尤其软内核)就必须仔细斟酌。

保护手段方面:参数化内核用基于 FlexLM 密钥的方式允许/禁止单个 IP 的生成,并允许”先试后买”——例如提供有时间限制的编程文件,或要求主机与开发板通过 JTAG 电缆永久连接。Altera 的 OpenCore 评估功能允许在目标系统内仿真 IP 内核功能、验证设计并估计规模与速度;满意并投产时再购买许可证,生成不受时间限制的编程文件。Quartus 会自动从 Altera 网站下载最新版 IP;许多第三方供应商也支持 OpenCore,但需直接联系对方启用。软内核的保护更困难:可以把 HDL 改写得难以阅读,也有人建议在高级设计中把外部硬件最小化并嵌入水印;水印必须健壮——水印中某一位的改变不应破坏对拥有者的授权。

5. 基于知识产权内核的 NCO 设计

最后回到上一节的频率合成器案例,这次改用 Altera 的 NCO 内核生成器。NCO 编译器生成针对 Altera 器件优化的数控振荡器,可选用基于 ROM、基于 CORDIC 或基于乘法器的多种体系结构;MegaWizard 还能根据参数设置动态显示 NCO 的时域、频域图形。

操作流程:新建项目 NCO,单击 Tools 菜单的 MegaWizard Plug-In Manager;第一步在窗口中选中 NCO 模块(见图 1-22(a),位于 DSP 内核下的 Signal Generation 组),选择输出格式(AHDL/VHDL/Verilog)和工作目录。随后进入 IP 工具平台窗口(图 1-22(b)),可查阅文档并开始第 1 步——给模块赋参数。要复现上一节的函数发生器,需选择 32 位累加器;但该内核最小输出位宽为 10 位,无法使用原设计的 8 位输出。参数窗口选择 Large ROM 生成算法(图 1-23)。与 fun_text 相同,时钟 100MHz、输出周期 20 个时钟(即 5MHz)。关于相位抖动(phase dithering,往相位低位注入随机噪声,让量化噪声更均匀地分布):开启后旁瓣抑制约 60dB,但需要两倍的 LE;不开约 50dB——可在 NCO 窗口下方的 Frequency Domain Response 图中直接观察。Implementation 窗口选 Single Output(I/Q 接收机只需正弦不需要余弦,见第 7 章)。Resource Estimation 给出:72 个 LE、2560 个存储器位、1 个 M9K 模块。第 2 步可选择生成行为 HDL 以加快仿真;本设计较小,跳过该选项直接生成完整 HDL。第 3 步点 Generate,生成的文件见表 1-14。


(a) 库元件的选取


(b) IP 工具平台 图 1-22 NCO 的 IP 设计


图 1-23 根据前一节案例研究数据设置 NCO 内核的 IP 参数

表 1-14 为 NCO 内核生成的 IP 文件

文件说明
nco.vhd定制 IP 内核功能的 VHDL 顶层描述文件
nco.cmpIP 内核函数的 VHDL 组件声明
nco.bsfIP 内核函数的 Quartus II 符号文件
nco_st.v生成的 NCO 可综合网表
nco.vhoVHDL IP 函数仿真模型
nco_tb.vhdVHDL 测试平台
nco_vho_msim.tcl在 MODELSIM 中运行 VHDL 仿真模型的 TCL 脚本
nco_wave.doMODELSIM 波形文件
nco_model.mMATLAB 位精确模型
nco_tb.mMATLAB 测试平台
nco_sin.hexIntel 十六进制格式 ROM 初始化文件
nco.vecQuartus 向量文件
nco_nativelink.tclNativeLink 仿真测试平台
nco.qipQuartus 项目信息
nco.html列出所有已生成文件的 IP 函数报告

可以看到,除了设计文件本身,工具还一并生成了 MATLAB(位精确)和 ModelSim(周期精确)测试平台,铺好了验证路径。我们直接把内核用作顶层设计,省去在另一个设计中实例化、连线的麻烦。查看顶层 nco.vhd:除了预期的 clk、phi_inc_i 输入和 fsin_o 输出,还有几个一目了然的控制信号 reset_n、clken、out_valid。完整编译后做时序仿真,生成 nco.vho 文件;把实际资源与估计值对比:存储器与 M9K 数量与预计一致,LE 实际 88 个、估计 72 个,误差 18%。

仿真时启动 MODELSIM 并切换到 NCO 目录,在命令窗口键入 do nco_vho_msim.tcl:脚本编译若干库和设计文件,执行 22 000ns 的仿真。放大起始几个时钟周期(图 1-24),能看到输出有效延迟约 6 个时钟周期,以及正弦输出周期。把与函数发生器相同的 代入,输出周期同样是 20 个时钟。还发现了 IP 模块的一个小问题:输出是有符号数,而 D/A 转换器需要无符号数(准确地说是二进制偏移量编码)。软内核可以改 HDL,参数化内核没有这个选项;解决办法是在输出端追加一个加法器加常数 512,把输出搬移成偏移二进制格式。偏移二进制不是内核里可选的参数,而成了我们额外要做的设计工作——这正是使用参数化内核的典型体验:内核能省掉 90% 甚至更多的设计时间,但通常还需要一点额外的设计工作来满足项目的具体要求。


图 1-24 NCO IP 设计的测试平台,通过时序仿真验证

1.5 练习

注意:如果没有使用 Quartus II 软件的经验,请先参阅 1.4.3 节的案例研究。除特别说明外,Quartus II 综合分析均使用 Cyclone IV E 系列的 EP4CE115F29C7 器件。以下练习按主题分组简述,完整步骤请对照原书。

门级基础(1.1):只用两输入与非门实现全加器:(a) 为异或);(b) (+ 为或,× 为与);(c) 用与非门实现非门、与门和或门,说明与非门是通用逻辑;(d) 用或非门重复 (a)~(c);(e) 用或非门实现二选一多路器

工具流程入门(1.2、1.3):(1.2) 用 ModelSim-Altera 编译 HDL 文件 example——vlib work 建工作库、vcom example.vhd 编译,用 do example.do 0 做功能仿真;再用 Quartus 编译器完整编译后,do example.do 1 做时序仿真。(1.3) 编写仿真脚本 example.do,为 clk、a、b、op1 生成图 1-25 所示的波形并仿真,解释 a、b、op1 与 sum、d 之间的代数关系。


图 1-25 例 1.1 的波形文件

优化与约束(1.4、1.5):(1.4) 把 fun_text 的综合优化分别设为 Speed、Balance 或 Area,比较 Slow 85C 模型下的 Fmax 与 LE 使用情况并解释。(1.5) 参照 Altera 教程生成 SDC 文件,将时钟周期分别设为 20ns、10ns、5ns、3ns,每次都完整编译,用时钟报告查看时序裕量并记录资源变化。

ROM 文件格式(1.6):修改 fun_text,改用 lpm_rom 组件和 MIF 文件 sine.mif 仿真;再用 File | Open 打开 sine.mif(显示在 Memory Editor 中),File | Save As 存为 Intel 十六进制格式 sine.hex;改用 sine.hex 后仿真验证正确性。

加法器实测(1.7):用 Quartus II 设计 32 位加法器;添加 I/O 寄存器后测 Fmax,与例 1.2 的数据对比。

PREP 基准测试(1.8、1.9):这两个练习用工业界经典的 PREP 基准电路做”单级 vs 多级”的性能对比。

  • 基准 1(1.8)是数据通路电路:8 位 4 选 1 乘法器 + 8 位寄存器 + 移位寄存器,受移位/加载信号 sl 控制——sl=1 时内容循环左移一位(),触发器由 clk 上升沿触发、rst 异步复位,功能仿真见图 1-26(c)。
  • 基准 2(1.9)是计数器电路:两个 8 位寄存器分别保存计数启动和停止值,异步复位 rst,同步加载使能(ld、ldpre、ldcomp)由 clk 上升沿触发;2:1 乘法器(sel 控制)选择直接加载 data1 或来自寄存器的 data2;当计数器值与 ldcomp 寄存器值相等时启用加载。注意原书特别提醒:复位后所有寄存器为 0,ld 将一直为真,会强制计数器保持 0——不要想当然认为复位后就开始计数;仿真时压缩测试平台信号,使波形落在 1μs 范围内。
  • 两个基准都要求:(b) 用 TimeQuest Slow 85C 模型测单级设计的 Fmax 与资源(LE、乘法器、M4K/M9K),在 Speed、Balance、Area 三种综合选项中找出规模与速度的最优解,并分别在三种器件上仿真——Cyclone IV E 的 EP4CE115F29C7、Cyclone II 的 EP2C35F672C6、MAX7000S 的 EPM7128SLC84-7;(c)(d) 设计级数尽可能多的多级原理图,重复同样的测量与器件对比。


(c) 测试功能的测试平台 图 1-26 PREP 基准 1


(c) 测试功能的测试平台 图 1-27 PREP 基准 2

结构式与行为式 HDL(1.10):用结构风格(只用非门、与门、或门等基本门)和行为风格分别编写:(a) 2:1 多路复用器;(b) XNOR(同或)门;(c) 半加器;(d) 2:4 译码器。结构设计中可使用 Altera 的 a_74xx SSI 元件(元件名不能以数字开头,故加前缀 a_;端口名与类型可查安装目录下 libraries\vhdl\altera\maxplus2.vhd,端口为 a_1、a_2、a_3 等,数据类型 STD_LOGIC)。用功能仿真和 RTL Viewer(Tools 菜单 Netlist Viewers 下)验证功能。

语言模板(1.11):用 Edit→Insert Template 分别编译:(a) 三态缓冲器(Logic | Tri-State);(b) 带全部控制信号的触发器(Logic | Registers | Full-Featured Positive Edge Register with All Secondary Signals);(c) 二进制计数器(Full Designs | Arithmetic | Counters);(d) 带异步复位的状态机(Full Designs | State Machines)。同样用功能仿真和 RTL Viewer 验证。

帮助系统检索(1.12):用 Quartus 帮助的 search 研究下列设计的实现:(a) 14 种计数器(search | implementing sequential logic);(b) 手动状态赋值(enumsmch);(c) 锁存器(latchinf);(d) 进程语句中的计数器(proc | Using Process Statements);(e) CAM/RAM/ROM 实现(ram256x8);(f) 用户自定义元件(reg24);(g) 带 clr/load/preset 的寄存器(reginf);(h) 状态机(state_machine | Implementing…)。新建项目粘贴代码并编译;VHDL 代码需添加 STD_LOGIC_1164 IEEE 库才不报错。用功能仿真和 RTL Viewer 验证。

语法细节判断(1.13~1.16)

  • 1.13 判断 VHDL 标识符有效性:VHSIC、h333、A_B_C、XyZ、N#3(含非法字符 #)、My-name(含连字符)、BEGIN(保留字)、A__B(连续下划线)、ENTITI。
  • 1.14 判断 VHDL 字符串字面量有效性:如 B”11_00”、O”5678”(八进制每位 0~7,8 越界)、X”5678”、16#FfF#(大小写均可)、10#007#、5#12345#、2#0001_1111_#(下划线不能紧邻 #)、2#00_00# 等。
  • 1.15 确定表示以下整型所需的位数(** 为幂运算符):INTEGER RANGE 10 TO 20;-2**6 TO 2**4-1;-10 TO -5;-2 TO 15。
  • 1.16、1.17 判断表 1-15、表 1-16 中示例 VHDL 代码各行的错误(回答 Y/N),解释原因或给出正确代码。典型错误包括:PORTS 应为 PORT、实体结束缺分号、信号声明缺类型、信号赋值误用变量赋值符号 :=、进程内对输入端口赋值、END PROCESS 的标签不匹配、GENERIC 缺少 := 赋值符号、库与架构名不一致、组件例化端口映射顺序与名称混用、位向量索引越界(width 而非 width-1)等。

1.18 从过程语句推断电路:成本与关键路径

这一节是一道非常典型的”读代码画电路”练习。它想训练的能力是:看到一段顺序执行的进程代码,你脑子里能立刻浮现出它综合成硬件后的样子——用了几个加法器、信号要流过多远。这为什么重要?因为同样一行 a + b - c + d,综合工具可以把它实现成串行的一条长链,也可以实现成并行的短树,两者的速度和面积差别很大。如果你写代码时不知道自己”买”了几个加法器,就无法控制设计的成本和时序。

题目与端口

四个输入 a、b、c、d 都是 4 位向量,六个输出 u、v、w、x、y、z 也都是 4 位向量。原书 VHDL 改写为 Verilog 后的代码框架如下(原书的 VHDL 实体声明对应下面的模块端口声明):

// qv2g.v —— 原书 VHDL 改写为 Verilog
module qv2g (
    input  wire [3:0] a, b, c, d,
    output reg  [3:0] u, v, w, x, y, z
);
    // 四个进程分别写在下面各小节
endmodule

I/O 端口标签整理成表就是:输入 a[3:0]、b[3:0]、c[3:0]、d[3:0],输出 u[3:0]、v[3:0]、w[3:0]、x[3:0]、y[3:0]、z[3:0]

P0:u = a + b - c + d

// 原书 VHDL 改写为 Verilog
always @(*) begin
    u = a + b - c + d;
end

按语言从左到右的结合规则,这个表达式综合成一条串行链

  • 成本:3 个加法器/减法器,成本 = 3。
  • 关键路径:信号要依次穿过 3 个运算单元,延迟 = 3。

P1:v = (a + b) - (c - d)

// 原书 VHDL 改写为 Verilog
always @(*) begin
    v = (a + b) - (c - d);
end

括号改变了结构:(a+b)(c-d) 是两条互不依赖的支路,各需 1 个运算单元,它们算完后再把结果相减。

  • 成本:仍然是 3 个加法器/减法器,成本 = 3。
  • 关键路径:最长支路只有 2 级(先算一支,再做最后一次减法),延迟 = 2。

P0 和 P1 数学上等价、成本相同,但 P1 更快——这就是”加括号换结构”的意义。若进一步用分配律 ,还能保持同样的 2 级深度。

P2:w = a + b + c 与 x = a - b - c

// 原书 VHDL 改写为 Verilog
always @(*) begin
    w = a + b + c;
    x = a - b - c;
end

同一个进程里两条独立的赋值,综合出两套电路:

  • w:,2 个加法器,路径 2;

  • x:,2 个减法器,路径 2。

  • 成本:4。

  • 关键路径:2(每条支路各自的最坏情况)。

P3:先用中间变量 t1 = b + c

// 原书 VHDL 改写为 Verilog
reg [3:0] t1;
 
always @(*) begin
    t1 = b + c;
    y  = a + t1;
    z  = a - t1;
end

这里 b+c 只算一次,结果 t1 同时喂给加法和减法——综合工具会共享这一个加法器:

  • 成本:3(1 个加法器 + 1 个加法器 + 1 个减法器)。
  • 关键路径:第一级 t1(延迟 1),第二级 y/z(再延迟 1),延迟 = 2。

把 P2 和 P3 对比一下:如果 P2 的两条支路都先算 b±c 之类的公共子表达式,就能像 P3 一样省运算单元。中间变量是手工引导硬件共享的最直接手段。

四个进程汇总:

进程表达式成本关键路径
P033
P132
P2w、x 两条独立支路42
P3共享 32

1.19 为 STD_LOGIC_VECTOR 设计扩展函数库

为什么需要扩展函数

FPGA 里的加法器、乘法器要求操作数位宽一致。但实际运算中结果往往比操作数宽:两个 4 位数相乘最多 8 位,两个 4 位数相加最多 5 位。要”加宽”一个数,有两种方式:

  • 符号扩展(sign extension):把最高位(符号位)复制填满高位。用于有符号数,保证 −3 扩成 8 位后还是 −3。
  • 零扩展(zero extension):高位全部填 0。用于无符号数,值不变。

不用它们会怎样?直接把 4 位数塞进 8 位运算,高位被随机补位,正负号就乱了——这是初学者最常见的位宽 bug。

(a) 扩展函数的 Verilog 实现

原书用 VHDL 函数实现,改写为 Verilog 的函数(function)如下,这里以 4 位参数扩展到 SIZE 位为例(arg 是输入向量,size 是目标位宽):

// 原书 VHDL 改写为 Verilog
function [7:0] sign_ext;        // 符号扩展:4 位 -> 8 位
    input [3:0] arg;
    begin
        sign_ext = {{4{arg[3]}}, arg};
    end
endfunction
 
function [7:0] zero_ext;        // 零扩展:4 位 -> 8 位
    input [3:0] arg;
    begin
        zero_ext = {4'd0, arg};
    end
endfunction

核心是 Verilog 的复制拼接运算符 {{n{bit}}, arg}{4{arg[3]}} 把符号位 arg[3] 复制 4 份,再拼上原数。推广到任意目标位宽 SIZE,符号扩展的通式是:

(b) 用重载思想实现 * 和 /

VHDL 通过函数”重载”让 */ 也能作用于 STD_LOGIC_VECTOR。Verilog 的做法更直接:只要操作数声明为 signed(或使用带符号的系统习惯),*/% 就自动按有符号解释。等价的写法:

// 原书 VHDL 改写为 Verilog(重载 * 与 / 的思路)
wire signed [3:0] p = a4;            // a4 为 4 位输入
wire signed [7:0] prod = p * p;      // 有符号乘法,结果自动 8 位
wire signed [3:0] quot = p / 4'd2;   // 有符号除法

若坚持函数风格,也可以包一层:

// 原书 VHDL 改写为 Verilog
function signed [7:0] slv_mul;
    input signed [3:0] x, y;
    begin
        slv_mul = x * y;
    end
endfunction

(c) 测试平台

图 1-28 给出了包的测试平台。原书 VHDL 测试平台改写为等价的 Verilog testbench:实例化被测模块,给一组边界值(最大正数、最大负数、0 等)驱动输入,在仿真波形里核对扩展与乘除结果。

// 原书 VHDL 测试平台改写为 Verilog
`timescale 1ns/1ps
module slv_pack_tb;
    reg  [3:0] arg;
    wire [7:0] s_ext, z_ext;
    wire signed [7:0] prod;
 
    // 被测函数封装进被测模块 dut 中
    slv_pack dut (.arg(arg), .s_ext(s_ext), .z_ext(z_ext), .prod(prod));
 
    initial begin
        arg = 4'b0111; #10;   // +7:符号扩展应为 8'b0000_0111
        arg = 4'b1000; #10;   // -8:符号扩展应为 8'b1111_1000
        arg = 4'b0000; #10;   //  0:零扩展与符号扩展结果相同
        $stop;
    end
endmodule


(a) HDL 代码

上图中波形部分即 (b) 功能仿真效果:把负数 1000 符号扩展成 11111000 后再做算术,结果仍保持负值,说明扩展正确。

图 1-28 STD_LOGIC_VECTOR 包测试平台

1.20 为 STD_LOGIC_VECTOR 设计移位函数库

四种移位是什么

VHDL-1993 只给 BIT_VECTOR 定义了四种移位操作,题目要求为 STD_LOGIC_VECTOR 补齐同样的函数:

  • SLL(逻辑左移):整体左移,低位补 0。相当于无符号数 ×2。
  • SRL(逻辑右移):整体右移,高位补 0。相当于无符号数 ÷2。
  • SLA(算术左移):形式上与 SLL 类似(低位补 0,移出的是最低位)。
  • SRA(算术右移):右移但高位补符号位,保证有符号数 ÷2 后符号不变。

逻辑移位和算术移位的差别只在右移时体现:对 11100000(若视为 −32),SRL 得 00010000(+16),SRA 得 11110000(−16)。不用算术右移,有符号数的除 2 会瞬间”变号”。

题目还有两个附加要求:其一,STD_LOGIC_VECTOR 含高阻值 Z,函数要能原样搬运这些位;其二,移位量为负时要做相反方向的移位,例如 SRA(a, −2) 等价于 SLL(a, 2)。

Verilog 实现

Verilog 自带算术移位运算符 <<<>>>(对 signed 类型 >>> 补符号位),改写后的移位函数库如下:

// 原书 VHDL 移位库改写为 Verilog
function [7:0] sll_fun; input [7:0] a; input integer n;
    begin
        sll_fun = n >= 0 ? (a << n) : (a >> (-n));
    end
endfunction
 
function [7:0] srl_fun; input [7:0] a; input integer n;
    begin
        srl_fun = n >= 0 ? (a >> n) : (a << (-n));
    end
endfunction
 
function [7:0] sla_fun; input [7:0] a; input integer n;
    begin
        sla_fun = n >= 0 ? (a <<< n) : (a >>> (-n));
    end
endfunction
 
function [7:0] sra_fun; input signed [7:0] a; input integer n;
    begin
        sra_fun = n >= 0 ? (a >>> n) : (a <<< (-n));
    end
endfunction

每个函数都用三目运算符处理了”负移位量反向移”的要求,Z 值则由 Verilog 的位操作自然保留(0 移入不会清除 Z 位以外的原有 Z)。

(e) 测试平台与仿真结果

原书 VHDL 测试平台改写为 Verilog:

// 原书 VHDL 测试平台改写为 Verilog
`timescale 1ns/1ps
module shift_pack_tb;
    reg  signed [7:0] a;
    reg  integer shft;
    wire [7:0] sll2, srl2, sla2, sra2;
 
    shift_pack dut (.a(a), .shft(shft),
                    .sll2(sll2), .srl2(srl2),
                    .sla2(sla2), .sra2(sra2));
 
    initial begin
        a = 8'b11100ZZZ; shft = 2;  #10;
        a = 8'b11100ZZZ; shft = -2; #10;
        $stop;
    end
endmodule


(a) HDL 代码

对照波形数据验证一遍。输入

  • 当移位量 时:
    • SLA2 = 1111100Z?注意按算术左移,低 2 位补 0,原 Z 位逐位上移:结果为 再看波形给出的 ,说明该实现把符号位补到了低端——这正是”算术左移补符号位”的一种解释,与图 1-29 波形 一致;
    • SLL2 = 0011100Z(左移 2 位,低位补 0);
    • SRA2 = 100ZZZZZ(右移 2 位,高位补符号位 1,Z 随之下移);
    • SRL2 = 100ZZZ00(右移 2 位,高位补 0)。
  • 当移位量 时,输出两两互换:SRA2 的结果等于 时 SLL2 的结果,SRL2 等于 SLL2 的反向——正好验证了”负值反向移位”的设计要求。

图 1-29 STD_LOGIC_VECTOR 移位库测试平台(含 (b) 功能仿真结果)

1.21 判断进程综合出的电路类型

这一节训练”从 IF 语句结构推断时序元件”的能力。判断口诀只有三条:

  1. 敏感表里没有时钟沿、且所有分支都对信号赋值完整 → 组合逻辑
  2. 分支不完整(少了 ELSE)却又是电平敏感 → 锁存器(闭锁器)
  3. 出现 rising_edge(clk)触发器;再看他拍下的动作:q <= not q 是 T 触发器,普通赋值是 D 触发器;在时钟沿之外、由电平置 0/置 1 的,分别是同步复位 SR / 同步置位 SS(异步 AR/AS 则是不看时钟直接清零置位)。

被测代码(原书 VHDL 改写为 Verilog):

// 原书 VHDL 改写为 Verilog
module quiz (
    input  wire       a, b, c,
    input  wire [0:5] d,
    output reg  [0:5] q
);
 
// P0
always @(posedge a)          q[0] <= d[0];
 
// P1
always @(*) begin
    if (a) q[1] = d[1];
    else   q[1] = 1'b1;
end
 
// P2
always @(posedge b) begin
    if (a) q[2] <= 1'b0;
    else   q[2] <= c ? 1'b1 : d[1];
end
 
// P3
always @(posedge b) begin
    if (a)      q[3] <= 1'b1;
    else if (c) q[3] <= 1'b0;
    else        q[3] <= ~q[3];
end
 
// P4
always @(*) begin
    if (a) q[4] = d[4];
end
 
// P5
always @(posedge a) begin
    if (b) q[5] <= 1'b0;
    else   q[5] <= d[5];
end
 
endmodule

逐个分析:

  • P0:只有 posedge a 一个时钟沿,无任何复位/置位 → 简单 D 触发器,时钟 a,数据 d(0)。
  • P1:敏感表是电平信号 (a, d),IF/ELSE 分支完整,无时钟沿 → 组合逻辑(一个 2 选 1 数据选择器:a 为 1 出 d(1),否则出 1)。
  • P2:时钟沿是 b。a=‘1’ 时在时钟沿到来把 q 清 0——清零动作发生在时钟拍上,所以是同步复位(SR);否则按 c 选择装 1 或 d(1)。电路类型:D 触发器,时钟 b,SR(同步复位,复位值 0)
  • P3:时钟沿 b。a=‘1’ 时同步置 1 → 同步置位(SS);c=‘1’ 时同步清 0;两者都不满足时 ,即翻转。电路类型:T 触发器(带同步置位 SS、同步复位功能),时钟 b。
  • P4:电平敏感,但 IF 没有 ELSE——a=0 时 q(4) 保持原值 → 综合出战 锁存器,a 是使能(电平)端。
  • P5:时钟沿 a,b=‘1’ 时在时钟拍清 0 → D 触发器,时钟 a,SR(同步复位,由 b 控制)

按表 1-17 填写分类结果:

PROCESS电路类型CLKASARSSSR
P0D 触发器a
P1组合逻辑
P2D 触发器ba(复位值 0)
P3T 触发器ba(置位值 1)c(复位值 0)
P4锁存器—(a 为使能)
P5D 触发器ab(复位值 0)

P2 与 P5 最容易误判成异步复位:关键看清零语句是否写在时钟沿判断的内部。写在 rising_edge 里面就是同步复位;写在它的外层(不看时钟直接生效)才是异步复位。

1.22 MATLAB 矩阵运算练习

这一小节用 8 条 MATLAB 指令复习矩阵运算与 DSP 的两个核心概念:卷积和 DFT。先算出各变量:

a = -1:2:5
b = [ones(1,2), zeros(1,2)]
  • a = -1:2:5:从 −1 开始、步长 2、不超过 5,得 (1×4 行向量)。
  • b = [1 1 0 0](1×4 行向量)。

c:a * a’(内积)

a' 是 4×1 列向量,行乘列得到标量

这正是向量的平方模长

d:a .* a(点乘)

.*逐元素相乘,结果还是 1×4:

注意 c 是把 d 的四个元素再加起来:

e:a’ * a(外积)

列乘行得到 4×4 矩阵,元素为

它是对称矩阵,秩为 1——外积的典型特征。

f:conv(a, b)(线性卷积)

两个长度 4 的向量卷积,输出长度 。按定义 逐点计算:

以第 3 个元素为例:

g:fft(b)(离散傅里叶变换)

4 点 DFT,,代入 b = [1 1 0 0]:

第 0 个分量 2 是 b 的元素和;由于 b 是实序列,频谱关于直流共轭对称。

h:ifft(fft(a) .* fft(b))(循环卷积)

频域相乘等价于时域循环卷积(圆周卷积)。结果长度仍为 4:

验证(下标按模 4 循环):。✔

f 与 h 的关系——这道题真正想说的

比较 f = [−1, 0, 4, 8, 5, 0, 0](长度 7)和 h = [4, 0, 4, 8](长度 4):循环卷积是线性卷积”尾部绕回叠加”的结果。把 f 的后 4 项 [5, 0, 0, 0] 绕回加到前 4 项 [−1, 0, 4, 8] 上:,正是 h。这就是 FFT 快速卷积的原理——只要在频域乘积前先补零到长度 ,循环卷积就等于线性卷积;不补零,就会像这里一样发生混叠。用 FFT 做滤波(如 FIR)时,这一点直接决定输出对不对。