跳到主要内容

FPGA 综合与约束

综合是将 Verilog RTL 代码转换为门级网表的过程,约束则指导综合工具如何优化设计。本章介绍 FPGA 综合的基本概念、约束文件编写和时序分析方法。

综合基础

什么是综合?

综合是将高层次的硬件描述(RTL)转换为低层次的门级网表的过程。综合工具会根据目标 FPGA 器件的架构,将 Verilog 代码映射到具体的硬件资源上。

综合的三个阶段

  1. Elaboration(详细化):解析 Verilog 代码,构建设计层次结构
  2. Synthesis(综合):将 RTL 结构映射到通用逻辑门
  3. Technology Mapping(工艺映射):将通用逻辑门映射到 FPGA 特定资源

可综合代码

并非所有 Verilog 语法都能被综合成实际硬件。以下是可综合和不可综合结构的对比:

可综合结构

// 组合逻辑
assign y = a & b;

// 时序逻辑
always @(posedge clk) begin
q <= d;
end

// 状态机
always @(*) begin
case (state)
IDLE: next_state = start ? ACTIVE : IDLE;
ACTIVE: next_state = done ? IDLE : ACTIVE;
endcase
end

// 参数化模块
module counter #(parameter WIDTH = 8) (
input clk,
output [WIDTH-1:0] count
);

不可综合结构

// 延迟控制
assign #10 y = a & b; // 不可综合

// initial 块(通常不可综合,用于仿真)
initial begin
data = 0;
end

// 系统任务
$display("Value: %h", data); // 不可综合

// 时间相关
#100; // 不可综合

// 强度建模
assign (strong1, weak0) y = a; // 通常不可综合

综合友好代码风格

良好的代码风格可以改善综合结果的质量和可预测性。

1. 使用完整的敏感列表

// 推荐:使用 @(*) 自动推断
always @(*) begin
if (sel)
y = a;
else
y = b;
end

// 不推荐:手动列出
always @(a or b or sel) begin
if (sel)
y = a;
else
y = b;
end

2. 避免产生锁存器

// 错误:会产生锁存器
always @(*) begin
if (enable)
data_out = data_in;
// 缺少 else 分支
end

// 正确:完整的赋值
always @(*) begin
data_out = 8'b0; // 默认值
if (enable)
data_out = data_in;
end

3. 使用非阻塞赋值描述时序逻辑

// 正确:时序逻辑使用非阻塞赋值
always @(posedge clk) begin
q1 <= d;
q2 <= q1;
end

// 错误:时序逻辑使用阻塞赋值
always @(posedge clk) begin
q1 = d;
q2 = q1; // q1 和 q2 将同时更新为 d
end

4. 合理使用复位

// 同步复位
always @(posedge clk) begin
if (reset)
counter <= 0;
else if (enable)
counter <= counter + 1;
end

// 异步复位
always @(posedge clk or posedge reset) begin
if (reset)
counter <= 0;
else if (enable)
counter <= counter + 1;
end

综合工具

Vivado 综合

Xilinx Vivado 是主流的 FPGA 综合工具。

综合流程

# 创建项目
create_project my_project ./my_project -part xc7z020clg400-1

# 添加源文件
add_files ./rtl

# 添加约束文件
add_files -fileset constrs_1 ./constraints/top.xdc

# 设置顶层模块
set_property top top_module [current_fileset]

# 运行综合
launch_runs synth_1

# 等待综合完成
wait_on_run synth_1

# 打开综合结果
open_run synth_1

# 查看报告
report_utilization
report_timing

综合属性

Vivado 提供了丰富的综合属性来控制综合行为:

// 保持信号名称
(* keep = "true" *) wire [7:0] debug_signal;

// 禁止优化
(* dont_touch = "true" *) reg [7:0] preserved_reg;

// RAM 风格
(* ram_style = "block" *) reg [7:0] mem [0:255];
(* ram_style = "distributed" *) reg [7:0] small_mem [0:15];

// ROM 风格
(* rom_style = "block" *) reg [7:0] rom_data;

// 使用 DSP 资源
(* use_dsp = "yes" *) reg [31:0] product;

// 流水线寄存器
(* shreg_extract = "no" *) reg [7:0] shift_reg [0:7];

// 最大扇出
(* max_fanout = 16 *) reg control_signal;

Quartus 综合

Intel Quartus 是另一个主流 FPGA 综合工具。

综合流程

# 创建项目
project_new my_project -overwrite

# 设置器件
set_global_assignment -name FAMILY "Cyclone V"
set_global_assignment -name DEVICE 5CEBA4F23C7

# 添加源文件
set_global_assignment -name VERILOG_FILE rtl/top.v

# 添加约束文件
set_global_assignment -name SDC_FILE constraints/top.sdc

# 运行综合
execute_module -tool map

# 查看报告
load_report

综合属性

// 保持信号
(* preserve *) reg [7:0] preserved_reg;

// RAM 块推断
(* ramstyle = "M10K" *) reg [7:0] mem [0:255];

// DSP 块推断
(* multstyle = "dsp" *) reg [31:0] product;

Yosys 开源综合

Yosys 是开源的 Verilog 综合工具。

综合脚本

# 读取设计
read_verilog top.v

# 详细化
hierarchy -check -top top

// 高级优化
proc
opt
fsm
opt

// 存储器映射
memory
opt

// 工艺映射
techmap
opt

// 清理
clean

// 输出
write_verilog synth.v
write_json synth.json

约束文件

约束文件用于指导综合和实现工具如何处理设计。常见的约束包括时序约束、引脚约束和面积约束。

XDC 约束文件

Xilinx 使用 XDC(Xilinx Design Constraints)格式。

时序约束

时钟定义

# 创建主时钟
create_clock -period 10.000 -name sys_clk [get_ports clk]

# 创建虚拟时钟(用于输入输出延迟)
create_clock -period 20.000 -name virtual_clk

# 生成时钟(PLL/MMCM 输出)
create_generated_clock -name pll_clk \
-source [get_pins pll_inst/CLKIN1] \
-multiply_by 2 \
[get_pins pll_inst/CLKOUT0]

输入延迟

# 设置输入延迟
set_input_delay -clock sys_clk -max 2.000 [get_ports data_in]
set_input_delay -clock sys_clk -min 0.500 [get_ports data_in]

# 输入延迟(相对于时钟下降沿)
set_input_delay -clock sys_clk -clock_fall -max 2.000 [get_ports data_in]

输出延迟

# 设置输出延迟
set_output_delay -clock sys_clk -max 1.500 [get_ports data_out]
set_output_delay -clock sys_clk -min -0.500 [get_ports data_out]

时钟不确定性

# 设置时钟不确定性
set_clock_uncertainty -setup 0.200 [get_clocks sys_clk]
set_clock_uncertainty -hold 0.100 [get_clocks sys_clk]

多周期路径

# 设置多周期路径(建立时间)
set_multicycle_path -setup 2 -from [get_cells reg_a] -to [get_cells reg_b]

# 设置多周期路径(保持时间)
set_multicycle_path -setup 2 -hold 1 -from [get_cells reg_a] -to [get_cells reg_b]

虚假路径

# 设置虚假路径(不需要时序检查)
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]

# 异步复位路径
set_false_path -from [get_ports reset]

最大/最小延迟

# 设置最大延迟
set_max_delay -from [get_cells src_reg] -to [get_cells dst_reg] 5.000

# 设置最小延迟
set_min_delay -from [get_cells src_reg] -to [get_cells dst_reg] 1.000

引脚约束

# 引脚位置约束
set_property PACKAGE_PIN W5 [get_ports clk]

# 电平标准
set_property IOSTANDARD LVCMOS33 [get_ports clk]

# 上拉/下拉
set_property PULLUP true [get_ports reset_n]

# 驱动强度
set_property DRIVE 12 [get_ports data_out]

# 转换速率
set_property SLEW FAST [get_ports data_out]

// 差分对
set_property IOSTANDARD LVDS [get_ports data_p]
set_property IOSTANDARD LVDS [get_ports data_n]

面积约束

// 设置 Pblock 约束区域
create_pblock pblock_accelerator
add_cells_to_pblock [get_pblocks pblock_accelerator] [get_cells accelerator_inst]
resize_pblock [get_pblocks pblock_accelerator] -add {CLOCKREGION_X0Y0:CLOCKREGION_X0Y1}

SDC 约束文件

Synopsys Design Constraints(SDC)是一种标准的约束格式,被 Quartus 等工具支持。

# 时钟定义
create_clock -name sys_clk -period 10.000 [get_ports clk]

# 输入延迟
set_input_delay -clock sys_clk -max 2.000 [get_ports data_in]
set_input_delay -clock sys_clk -min 0.500 [get_ports data_in]

# 输出延迟
set_output_delay -clock sys_clk -max 1.500 [get_ports data_out]
set_output_delay -clock sys_clk -min -0.500 [get_ports data_out]

# 时钟不确定性
set_clock_uncertainty -setup 0.200 [get_clocks sys_clk]

# 多周期路径
set_multicycle_path -setup 2 -from [get_registers reg_a] -to [get_registers reg_b]

# 虚假路径
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]

时序分析

静态时序分析(STA)

静态时序分析是验证设计是否满足时序要求的关键步骤。

建立时间和保持时间

建立时间(Setup Time):数据必须在时钟边沿之前稳定的最小时间。

保持时间(Hold Time):数据必须在时钟边沿之后保持稳定的最小时间。

建立时间检查

数据必须在时钟边沿之前到达:

TarrivalTclockTsetupT_{arrival} \leq T_{clock} - T_{setup}

其中:

  • TarrivalT_{arrival} 是数据到达时间
  • TclockT_{clock} 是时钟周期
  • TsetupT_{setup} 是建立时间

计算数据到达时间

Tarrival=Tclk_q+TcombT_{arrival} = T_{clk\_q} + T_{comb}

其中:

  • Tclk_qT_{clk\_q} 是源寄存器的时钟到输出延迟
  • TcombT_{comb} 是组合逻辑延迟

建立时间余量

Slacksetup=TclockTsetupTarrivalSlack_{setup} = T_{clock} - T_{setup} - T_{arrival}

如果 Slacksetup0Slack_{setup} \geq 0,则满足建立时间要求。

保持时间检查

数据必须在时钟边沿之后保持稳定:

TarrivalTholdT_{arrival} \geq T_{hold}

保持时间余量

Slackhold=TarrivalTholdSlack_{hold} = T_{arrival} - T_{hold}

如果 Slackhold0Slack_{hold} \geq 0,则满足保持时间要求。

时序报告解读

Vivado 时序报告示例:

Timing Summary Report
----------------------
Design Timing Summary
---------------------
Setup WNS: 0.250 ns
Hold WNS: 0.150 ns
Pulse Width WNS: 0.000 ns

Max Delay Path
--------------
Source: reg_a_reg/C
Destination: reg_b_reg/D
Path Group: sys_clk
Path Type: Setup (Max)

Slack (MET): 0.250ns
Source Clock: sys_clk (rise edge)
Destination Clock: sys_clk (rise edge)
Clock Pessimism Removal: 0.000ns
Clock Uncertainty: 0.100ns

Location Delay Type Incr(ns) Path(ns)
----------------------------------------------------
(clock sys_clk) source 0.000 0.000
reg_a_reg/C CK (r) 0.000 0.000
reg_a_reg/Q Q (r) 0.200 0.200
...comb logic... 2.500 2.700
reg_b_reg/D D (r) 0.000 2.700
----------------------------------------------------
arrival time 2.700

(clock sys_clk) destination 10.000 10.000
reg_b_reg/CK CK (r) 0.000 10.000
library setup time -0.150 9.850
----------------------------------------------------
required time 9.850
Slack 0.250

时序违例修复

建立时间违例

如果建立时间违例(WNS < 0),可以采取以下措施:

  1. 降低时钟频率
# 增加时钟周期
create_clock -period 12.000 -name sys_clk [get_ports clk]
  1. 流水线设计

将长组合逻辑路径分割成多个阶段:

// 违例的长路径
always @(posedge clk) begin
result <= a * b + c * d; // 组合逻辑过长
end

// 改为流水线
always @(posedge clk) begin
// 第一级
product_a <= a * b;
product_c <= c * d;
// 第二级
result <= product_a + product_c;
end
  1. 使用 DSP 资源
(* use_dsp = "yes" *) reg [31:0] product;
  1. 减少逻辑层数

优化组合逻辑表达式,减少逻辑深度。

保持时间违例

如果保持时间违例,可以采取以下措施:

  1. 增加数据路径延迟

添加缓冲器或延迟单元:

// 添加延迟链
(* dont_touch = "true" *) reg [7:0] delay_chain;
  1. 调整时钟偏斜

确保时钟树平衡,减少时钟偏斜。

资源利用

FPGA 资源类型

现代 FPGA 包含以下主要资源:

资源类型描述用途
LUT查找表组合逻辑
FF触发器时序逻辑
BRAM块存储器大容量存储
DSP数字信号处理乘法、累加
PLL/MMCM时钟管理时钟生成
IOBI/O 块输入输出

资源使用分析

Vivado 资源报告

# 查看资源使用情况
report_utilization -hierarchical

# 查看特定资源
report_utilization -hierarchical -hierarchical_depth 2 -cells [get_cells *]

资源使用优化

// 使用 BRAM 存储器
(* ram_style = "block" *) reg [7:0] mem [0:4095];

// 使用 DSP 进行乘法
(* use_dsp = "yes" *) reg [31:0] product;

// 共享资源
// 将多个乘法器复用一个 DSP
reg [31:0] product;
always @(*) begin
case (sel)
2'b00: product = a * b;
2'b01: product = c * d;
2'b10: product = e * f;
default: product = 0;
endcase
end

面积优化策略

  1. 资源共享:多个操作复用同一硬件
  2. 状态编码优化:选择合适的状态编码
  3. 逻辑简化:简化布尔表达式
  4. 使用专用资源:DSP、BRAM 等

设计优化

性能优化

  1. 流水线技术

将长路径分割成多个短路径:

module pipeline_design (
input clk,
input [31:0] data_in,
output [31:0] data_out
);
reg [31:0] stage1, stage2, stage3;

always @(posedge clk) begin
stage1 <= data_in;
stage2 <= stage1;
stage3 <= stage2;
end

assign data_out = stage3;
endmodule
  1. 并行处理

使用并行结构提高吞吐量:

// 串行处理
always @(posedge clk) begin
for (i = 0; i < 8; i = i + 1) begin
result <= result + data[i];
end
end

// 并行处理
always @(*) begin
result = data[0] + data[1] + data[2] + data[3] +
data[4] + data[5] + data[6] + data[7];
end
  1. 寄存器复制

减少高扇出信号的负载:

// 复制寄存器降低扇出
(* max_fanout = 8 *) reg enable;
reg enable_d1, enable_d2, enable_d3;

always @(posedge clk) begin
enable_d1 <= enable;
enable_d2 <= enable;
enable_d3 <= enable;
end

功耗优化

  1. 时钟门控
module clock_gating (
input clk,
input enable,
output gated_clk
);
reg enable_d;

always @(posedge clk) begin
enable_d <= enable;
end

// 使用专用的时钟门控单元
// 或由综合工具自动推断
assign gated_clk = clk & enable_d;
endmodule
  1. 操作数隔离
// 只在需要时启用计算
wire [31:0] result = enable ? (a * b) : 32'b0;
  1. 状态机优化

减少不必要的状态转换。

综合报告分析

查看综合报告

综合报告包含以下关键信息:

  1. 资源使用报告
Resource Utilization Summary
----------------------------
Resource Used Avail Util%
LUT 1234 53200 2.32%
LUTRAM 56 17400 0.32%
FF 2345 106400 2.20%
BRAM 8 140 5.71%
DSP 4 220 1.82%
IO 24 200 12.00%
  1. 时序报告
Timing Summary
--------------
WNS (Worst Negative Slack): 0.250 ns
TNS (Total Negative Slack): 0.000 ns
WHS (Worst Hold Slack): 0.150 ns
THS (Total Hold Slack): 0.000 ns
  1. 功耗报告
Power Summary
-------------
Total On-Chip Power: 1.234 W
Dynamic Power: 0.856 W
Static Power: 0.378 W

常见综合问题

  1. 推断出锁存器

原因:不完整的条件赋值

解决:确保所有分支都有赋值

  1. 时序违例

原因:组合逻辑过长、时钟频率过高

解决:流水线、降低频率、优化逻辑

  1. 资源使用过高

原因:设计过于复杂

解决:资源共享、使用专用资源

  1. 综合结果与仿真不一致

原因:使用了不可综合的结构、仿真和综合语义差异

解决:检查代码风格,避免 X/Z 值传播

小结

本章介绍了 FPGA 综合与约束:

  • 综合基础:综合流程、可综合代码、代码风格
  • 综合工具:Vivado、Quartus、Yosys
  • 约束文件:XDC、SDC 格式,时序约束、引脚约束
  • 时序分析:建立时间、保持时间、时序报告
  • 资源利用:LUT、FF、BRAM、DSP
  • 设计优化:性能优化、功耗优化

掌握综合与约束知识是 FPGA 设计成功的关键。下一章将介绍测试平台编写。