B3: 时序分析和优化
一、阶段目标
B3 阶段的核心目标是使用开源 EDA 工具对 NPC 处理器进行逻辑综合和静态时序分析 (STA),评估设计的时序表现并进行优化。完成后你将:
- 理解逻辑综合的基本概念(RTL → 门级网表)
- 理解静态时序分析 (STA) 的原理和方法
- 掌握 SDC 时序约束文件的编写
- 使用 Yosys + iEDA 工具链完成综合和 STA
- 识别关键路径并掌握常见优化手段
- 理解面积/时序/功耗三者的 trade-off
- 评估处理器设计在目标工艺下的最高工作频率
二、B3 与 B2 的承接关系
2.1 B2 完成了什么?
B2 阶段完成了完整的 SoC 集成:
- CPU 核通过 AXI4 接口对接 ysyxSoC 框架
- 多种存储器(Flash/PSRAM/SDRAM)和外设(UART16550/SPI)
- 两级 Bootloader 启动流程
- 功能验证通过(cpu-tests + DiffTest)
2.2 B3 要解决什么问题?
| 问题 | 说明 |
|---|---|
| 设计能跑多快? | 之前只在仿真中验证功能正确性,不知道真实硬件频率 |
| 有没有时序违例? | 某些组合逻辑路径可能太长,导致无法满足时钟约束 |
| 关键路径在哪? | 需要知道哪些模块/信号是性能瓶颈 |
| 如何优化? | 插入流水寄存器、优化组合逻辑深度 |
| 面积多大? | 综合后可以评估芯片面积(等效门数) |
2.3 为什么在 B2 之后做 B3?
功能正确 (B1/B2) → 时序评估 (B3) → 性能优化 (B4/B5)
↓
发现瓶颈 → 指导 B4 的 Cache 设计和 B5 的流水线优化
B3 的时序分析结果会反馈到后续阶段:
- 如果 IFU 到 IDU 的组合逻辑太长 → B5 需要在此插入流水级
- 如果 AXI 互联是瓶颈 → 需要增加 Spill Register
- 如果 ALU 延迟大 → 可能需要多周期执行
三、逻辑综合基础
3.1 什么是逻辑综合?
逻辑综合是将 RTL 描述(Verilog/SystemVerilog)转换为由标准单元库中的门级元件组成的网表的过程。
RTL (Verilog) 门级网表 (Netlist)
┌────────────────┐ ┌────────────────────────────┐
│ always @(posedge clk)│ │ DFF_X1 u1 (.D(n1), .Q(q));│
│ q <= a & b; │ ──→ │ AND2_X1 u2 (.A(a), │
│ │ (综合) │ .B(b), .Z(n1));│
└────────────────┘ └────────────────────────────┘
3.2 综合的输入和输出
| 输入 | 说明 |
|---|---|
| RTL 源文件 | Verilog/SystemVerilog 设计文件 |
| 标准单元库 (.lib) | 描述每个门的延迟、面积、功耗 |
| 时序约束 (.sdc) | 时钟频率、输入/输出延迟 |
| 输出 | 说明 |
|---|---|
| 门级网表 (.v) | 由标准单元组成的 Verilog |
| 面积报告 | 等效门数、单元类型统计 |
| 时序报告 | 关键路径、WNS、TNS |
3.3 标准单元库 (nangate45)
本项目使用开源 NanGate 45nm 工艺库:
| 单元类型 | 举例 | 说明 |
|---|---|---|
| 组合逻辑 | AND2_X1, OR2_X1, INV_X1 | 与门、或门、反相器 |
| 时序逻辑 | DFF_X1, DFFR_X1 | D 触发器 (带/不带复位) |
| 缓冲器 | BUF_X1, BUF_X2, BUF_X4 | 不同驱动强度的缓冲 |
| 特殊单元 | LOGIC0_X1, LOGIC1_X1 | 常量 0/1 驱动 |
| 时钟门控 | CLKGATE_X1 | 时钟门控单元 |
X1/X2/X4 后缀: 表示驱动强度。X1 面积最小但驱动弱,X4 面积大但驱动强。综合器根据负载自动选择。
3.4 综合流程(本项目的 Yosys 脚本)
# yosys-sta/scripts/yosys.tcl 的核心步骤:
# 1. 读入 RTL
read_verilog -sv $RTL_FILES
# 2. 通用综合 (与工艺无关的优化)
synth -top $DESIGN # 推断寄存器、合并逻辑
# 3. 优化
opt -purge # 删除冗余逻辑
# 4. 技术映射 — 触发器
dfflibmap -liberty $LIB # DFF → 标准单元库的触发器
# 5. 技术映射 — 组合逻辑
abc -D $CLK_PERIOD_PS \ # 延迟约束下的逻辑优化
-liberty $LIB \
-script $ABC_SCRIPT # ABC 优化脚本 (retime, map, size)
# 6. 插入 tie 单元
hilomap -hicell LOGIC1_X1 Z \
-locell LOGIC0_X1 Z
# 7. 输出
write_verilog $NETLIST # 写出网表
stat -liberty $LIB # 面积统计报告
四、静态时序分析 (STA)
4.1 什么是 STA?
静态时序分析 不执行仿真,而是通过分析电路中所有路径的延迟,判断设计是否能在给定时钟频率下正确工作。
时钟周期 T = 1/频率
Setup 约束: 数据必须在时钟沿之前 t_setup 时间到达
Data Arrival Time + t_setup ≤ T
Hold 约束: 数据必须在时钟沿之后 t_hold 时间内保持稳定
Data Arrival Time ≥ t_hold
4.2 关键术语
| 术语 | 全称 | 含义 |
|---|---|---|
| WNS | Worst Negative Slack | 最差负时序裕量(<0 表示违例) |
| TNS | Total Negative Slack | 所有违例路径的裕量之和 |
| Slack | — | 时序裕量 = 要求的时间 - 实际到达时间 |
| 关键路径 | Critical Path | 延迟最长的路径(决定最高频率) |
| Fmax | Maximum Frequency | 设计能工作的最高频率 |
| Setup Time | — | 数据在时钟沿前必须稳定的最小时间 |
| Hold Time | — | 数据在时钟沿后必须保持的最小时间 |
4.3 路径类型
┌─────────┐ ┌─────────┐
│ Reg A │──组合逻辑──▶│ Reg B │
│ (源) │ (延迟) │ (目标) │
└─────────┘ └─────────┘
路径延迟 = t_clk→Q(A) + t_组合逻辑 + t_setup(B)
满足约束条件: 路径延迟 ≤ T (时钟周期)
三类路径:
- Reg-to-Reg: 寄存器输出 → 组合逻辑 → 寄存器输入(最常见)
- Input-to-Reg: 外部输入端口 → 组合逻辑 → 寄存器输入
- Reg-to-Output: 寄存器输出 → 组合逻辑 → 外部输出端口
4.4 时序分析报告解读
典型的 STA 报告格式:
========================================
Timing Report (Setup Check)
========================================
Clock: core_clock, Period: 10.00 ns (100 MHz)
Startpoint: u_top/u_idu/o_exu_opt_reg[3]
Endpoint: u_top/u_exu/u_alu/result_reg[31]
Path Type: max (setup check)
Data Path:
Pin Delay Arrival
─────────────────────────────────────────────
u_idu/o_exu_opt_reg[3]/Q 0.12 0.12 (源寄存器 clk→Q)
u_idu/mux_gate_1/Z 0.35 0.47 (组合逻辑)
u_exu/u_alu/add_gate/CO 0.89 1.36 (ALU 加法进位链)
u_exu/u_alu/result_reg[31]/D 0.05 1.41 (目标寄存器 D 端)
─────────────────────────────────────────────
Data Required Time: 10.00
Data Arrival Time: 1.41
─────────────────────────────────────────────
Slack: 8.59 ns (PASS)
WNS = +2.86 ns (最差路径也满足约束)
TNS = 0.00 ns (无违例)
Estimated Fmax = 1000 / (10.00 - 2.86) = 140 MHz
4.5 Fmax 计算
Fmax = 1 / (T - WNS)
其中:
T = 时钟约束周期
WNS = 最差负裕量 (正值表示有余量)
例如:
约束 100 MHz (T = 10ns), WNS = +2.86ns
→ 最长路径实际延迟 = 10 - 2.86 = 7.14ns
→ Fmax = 1/7.14ns ≈ 140 MHz
约束 500 MHz (T = 2ns), WNS = +0.14ns
→ 最长路径 = 2 - 0.14 = 1.86ns
→ Fmax = 1/1.86ns ≈ 538 MHz (nangate45 理想值)
五、SDC 时序约束文件
5.1 什么是 SDC?
SDC (Synopsys Design Constraints) 是工业标准的时序约束文件格式,用于告诉综合器和 STA 工具设计的时序要求。
5.2 项目的 SDC 文件 (top.sdc)
# npc/top.sdc — NPC 处理器的时序约束
# 定义时钟端口名称
set clk_port_name clock
# 读取目标频率 (由 Makefile 传入)
set CLK_FREQ_MHZ 100
if {[info exists env(CLK_FREQ_MHZ)]} {
set CLK_FREQ_MHZ $::env(CLK_FREQ_MHZ)
}
# I/O 延迟占比 (输入输出延迟占时钟周期的比例)
set clk_io_pct 0.2
# 获取时钟端口
set clk_port [get_ports $clk_port_name]
# 创建时钟约束
create_clock -name core_clock \
-period [expr 1000.0 / $CLK_FREQ_MHZ] \
$clk_port
# 100 MHz → period = 10.0 ns
# 200 MHz → period = 5.0 ns
# 500 MHz → period = 2.0 ns
5.3 SDC 命令详解
| 命令 | 说明 | 示例 |
|---|---|---|
create_clock |
定义时钟信号 | -period 10.0 (100MHz) |
set_input_delay |
输入端口到达时间约束 | 20% 时钟周期 |
set_output_delay |
输出端口建立时间约束 | 20% 时钟周期 |
set_max_fanout |
最大扇出约束 | 限制单个信号的负载 |
set_max_transition |
最大转换时间约束 | 限制信号上升/下降沿 |
5.4 时钟端口名称的重要性
SDC 中的 clk_port_name 必须与 RTL 中的端口名一致:
// top.v 中:
module top (
input clock, // ← SDC 中必须用 "clock"
input reset
);
如果 SDC 写成 set clk_port_name clk 而 RTL 用 clock,STA 工具找不到时钟端口,分析结果会错误。
六、项目的综合和 STA 工具链
6.1 工具链架构
┌─────────────────────────────────────────┐
│ yosys-sta 工具链 │
│ │
RTL (.v/.sv) ──────▶│ ┌───────┐ ┌─────┐ ┌──────┐ │
│ │ Yosys │────▶│ iNO │────▶│ iSTA │ │
SDC (.sdc) ────────▶│ │(综合) │ │(优化)│ │(分析)│ │
│ └───────┘ └─────┘ └──────┘ │
nangate45 (.lib) ──▶│ │ │ │ │
│ ▼ ▼ ▼ │
│ 网表.syn.v 网表.fixed.v 报告.rpt │
│ 面积报告 扇出优化 时序路径 │
└─────────────────────────────────────────┘
6.2 三个阶段
| 阶段 | 工具 | 输入 | 输出 | 说明 |
|---|---|---|---|---|
| 综合 | Yosys | RTL + .lib | 网表 + 面积报告 | RTL → 门级 |
| 扇出优化 | iNO (iEDA) | 网表 + SDC | 优化网表 | 插入缓冲器修复高扇出 |
| 时序分析 | iSTA (iEDA) | 优化网表 + SDC + .lib | 时序报告 | 计算所有路径延迟 |
6.3 运行命令
# 在项目根目录运行:
cd npc
make syn
# 等价于:
cd yosys-sta
make sta DESIGN=top \
SDC_FILE=$NPC_HOME/top.sdc \
RTL_FILES="$(find $NPC_HOME/vsrc -path '*/deprecated' -prune -o -name '*.v')" \
CLK_FREQ_MHZ=100 \
INCLUDE_PATH=$NPC_HOME/vsrc/include
6.4 输出文件
综合完成后,在 yosys-sta/result/top-100MHz/ 目录下会生成:
| 文件 | 说明 |
|---|---|
top.netlist.syn.v |
Yosys 综合网表 |
synth_stat.txt |
面积统计报告(单元数/面积) |
synth_check.txt |
综合检查报告(需要人工审核警告) |
yosys.log |
Yosys 完整日志 |
top.netlist.fixed.v |
iNO 扇出优化后的网表 |
fix-fanout.log |
扇出优化日志 |
top.rpt |
iSTA 时序分析报告(WNS/TNS/路径) |
top.cap |
电容违例报告 |
top.fanout |
扇出违例报告 |
top.trans |
转换时间违例报告 |
top_hold.skew |
Hold 模式时钟偏斜报告 |
top_setup.skew |
Setup 模式时钟偏斜报告 |
sta.log |
iSTA 完整日志 |
6.5 安装依赖
# Ubuntu/Debian:
apt install yosys # 开源综合器
apt install libunwind-dev libyaml-cpp-dev \
libgomp1 libtcl8.6 # iEDA 依赖库
# 下载 iEDA 二进制和工艺库:
cd yosys-sta
make init # 从 ysyx 官网下载
七、NPC 中的关键路径分析
7.1 典型关键路径
在处理器设计中,以下路径通常是时序瓶颈:
┌────────────────────────────────────────────────────────────────────┐
│ 路径 1: ALU 运算路径 (最常见瓶颈) │
│ │
│ IDU译码 → 操作数选择MUX → ALU运算(加法进位链) → 结果输出 │
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ │
│ 约 2-4ns (取决于 ALU 实现和操作数宽度) │
├────────────────────────────────────────────────────────────────────┤
│ 路径 2: 分支决策路径 │
│ │
│ EXU结果 → Zero判断 → BRU分支决策 → PC更新 │
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ │
│ 约 2-3ns │
├────────────────────────────────────────────────────────────────────┤
│ 路径 3: AXI 地址解码路径 │
│ │
│ 请求地址 → addr_decode匹配 → Demux选择 → Slave端口 │
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ │
│ 约 1-2ns (组合逻辑地址比较) │
├────────────────────────────────────────────────────────────────────┤
│ 路径 4: LSU 数据对齐路径 │
│ │
│ 内存返回数据 → 字节选择/符号扩展 → WBU写回寄存器堆 │
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ │
│ 约 1-2ns │
└────────────────────────────────────────────────────────────────────┘
7.2 项目实测结果
根据 Git 记录 (4637c62):
- 目标约束: 100 MHz (T = 10ns)
- 综合结果: Fmax ≈ 350 MHz (理想,nangate45)
- 最长路径约 2.86ns → WNS ≈ +7.14ns @100MHz
注意: nangate45 是学术工艺库,实际流片工艺的延迟会不同。350MHz 是该工艺下的理论值,实际 FPGA 频率通常低得多(50-200MHz)。
7.3 面积报告解读
=== top ===
Number of wires: 12345
Number of wire bits: 45678
Number of cells: 8901 ← 等效门数
Chip area for module '\top': 23456.78 ← 面积 (μm²)
Cell type usage:
DFF_X1: 512 ← 触发器数量 (≈寄存器位数)
AND2_X1: 234
OR2_X1: 189
INV_X1: 456
MUX2_X1: 123
NAND2_X1: 678
...
解读要点:
- DFF 数量 ≈ 设计中所有寄存器的总位数
- 面积受 ALU、寄存器堆、控制逻辑影响
- RV32E (16 reg × 32 bit = 512 bit RegFile) 面积小于 RV32I (32 reg)
八、时序优化手段
8.1 Spill Register(流水线寄存器)
原理: 在长组合路径中间插入寄存器,将一段路径拆分为两段,每段延迟更短。
优化前 (一段长路径):
Reg_A ──── 长组合逻辑 (8ns) ────── Reg_B
最高频率: 1/8ns = 125 MHz
优化后 (插入 Spill Register):
Reg_A ── 组合逻辑1 (4ns) ── Spill_Reg ── 组合逻辑2 (4ns) ── Reg_B
最高频率: 1/4ns = 250 MHz
代价: 增加 1 个时钟周期的延迟 (latency +1)
8.2 项目中的 Spill Register 实现
// npc/vsrc/libs/spill_register.sv (PULP Platform)
module spill_register #(
parameter type T = logic,
parameter bit Bypass = 1'b0 // Bypass=1 时透明(不插入寄存器)
) (
input logic clk_i,
input logic rst_ni,
input logic valid_i,
output logic ready_o,
input T data_i,
output logic valid_o,
input logic ready_i,
output T data_o
);
工作原理:
- 内部有 A、B 两个缓冲寄存器
- 完全切断输入和输出之间的组合逻辑路径
- 维护 valid/ready 握手语义(不丢数据、不重复数据)
Bypass=1时退化为直连(调试用)
8.3 AXI Demux 中的 Spill Register 应用
// top.v 中 AXI Demux 的实例化参数:
axi_demux_intf #(
// ...
.SPILL_AW(1), // AW 通道插入 Spill Register ← 切断地址解码路径
.SPILL_W (0), // W 通道不插入 (数据通道延迟不大)
.SPILL_B (0), // B 通道不插入
.SPILL_AR(1), // AR 通道插入 Spill Register ← 切断地址解码路径
.SPILL_R (0) // R 通道不插入
) u_axi_demux (...);
为什么只在 AW/AR 通道使能 Spill?
- AW/AR 通道包含地址信号,需要通过
addr_decode做地址比较(组合逻辑较深) - W/R/B 通道只是数据/响应传递,组合逻辑浅
- Spill 增加延迟,只在必要处使用
8.4 其他优化手段
| 手段 | 原理 | 代价 | 适用场景 |
|---|---|---|---|
| 插入 Spill Register | 切断长路径 | +1 cycle latency | AXI 通道、流水线间 |
| 逻辑重组 | 平衡组合逻辑树深度 | 可能增加面积 | ALU 加法器 |
| 寄存器重定时 | 移动寄存器位置均衡路径 | 改变时序语义 | 流水线 |
| 增大驱动强度 | 使用 X2/X4 单元 | 增加面积/功耗 | 高扇出信号 |
| 减少扇出 | 复制驱动源 | 增加面积 | 时钟/复位树 |
| 多周期路径声明 | 允许路径跨多个周期 | 需要正确约束 | 低速接口 |
8.5 Valid/Ready 握手的时序优势
本项目的 Valid/Ready 弹性流水线天然支持 Spill Register 插入:
之前 (全局 stall):
Reg → 组合逻辑 → Reg (所有级都绑在一起, 优化困难)
之后 (Valid/Ready):
Reg → 组合逻辑 → Spill → 组合逻辑 → Reg
↑
可以在任意点插入 Spill Register
而不影响功能正确性 (握手协议保证)
这是 B1 阶段选择 Valid/Ready 而非全局 Stall 的重要原因之一。
九、面积/时序/功耗 Trade-off
9.1 铁三角
时序 (速度)
/\
/ \
/ \
/ 选择 \
/ 空间 \
/ \
/____________\
面积 功耗
核心矛盾:
- 提升速度 → 使用更大的门(X2/X4)、插入缓冲 → 面积↑ 功耗↑
- 减小面积 → 使用更小的门(X1)、共享逻辑 → 速度↓
- 降低功耗 → 时钟门控、电压降低 → 速度↓ 或设计复杂度↑
9.2 在 NPC 设计中的体现
| 设计决策 | 选择 | 面积影响 | 时序影响 |
|---|---|---|---|
| RV32E (16 reg) vs RV32I (32 reg) | RV32E | 面积小 | 无影响 |
| 软件乘除法 vs 硬件乘除法 | 软件 | 面积极小 | 性能差 |
| Spill Register (AW/AR) | 使能 | 面积+少量 | 时序改善 |
| ICache (B4) | 组相联 | 面积大 | 性能好 |
| 单发射 vs 多发射 | 单发射 | 面积小 | IPC≤1 |
9.3 综合约束如何影响结果
# 约束 100 MHz (宽松):
make syn CLK_FREQ_MHZ=100
# 结果: 面积小, 综合器选择小单元, 不需要激进优化
# 约束 500 MHz (激进):
make syn CLK_FREQ_MHZ=500
# 结果: 面积大, 综合器选择大单元, 可能仍有违例
# 约束 0 (不约束, 最小面积):
make syn CLK_FREQ_MHZ=0
# 结果: 面积最小, 但时序无保证
十、综合中的常见问题
10.1 DPI-C 和仿真代码的处理
综合时 DPI-C 调用和 $write/$display 等仿真语句无法映射到硬件。需要通过条件编译排除:
// 项目中的处理方式 (defines.vh):
`ifdef SYNTHESIS
`define YSYXSOC // 综合时强制使用 SoC 模式
`endif
// 在外设中:
`ifndef SYNTHESIS
import "DPI-C" function void rtl_pmem_read(...);
always @(*) rtl_pmem_read(addr, data, en);
`else
// 综合时不包含 DPI-C 调用
`endif
10.2 综合检查报告 (synth_check.txt)
Yosys 的 check 命令会报告潜在问题:
| 警告类型 | 含义 | 是否需要关注 |
|---|---|---|
Warning: multiple drivers |
信号有多个驱动源 | ⚠️ 需要修复 |
Warning: undriven wire |
信号无驱动 | ⚠️ 检查是否遗漏 |
Warning: unused module port |
模块端口未连接 | ℹ️ 通常无害 |
Warning: loop detected |
组合逻辑环 | ❌ 必须修复 |
10.3 综合不可综合的构造
| 构造 | 可综合? | 替代方案 |
|---|---|---|
initial 块 |
❌ | 使用复位信号 |
$display / $write |
❌ | 条件编译排除 |
DPI-C import |
❌ | 条件编译排除 |
real 类型 |
❌ | 不使用浮点 |
delay (#10) |
❌ | 不使用延迟 |
for 循环 (可展开) |
✅ | 综合器自动展开 |
generate |
✅ | 参数化设计 |
always @(posedge clk) |
✅ | 推断为寄存器 |
always @(*) |
✅ | 推断为组合逻辑 |
10.4 Latch 推断警告
如果 always @(*) 块中有些分支没有赋值,综合器会推断出 Latch(锁存器)。这通常是 bug:
// 错误: 缺少 default → 推断 Latch
always @(*) begin
case (sel)
2'b00: out = a;
2'b01: out = b;
// 缺少 default! 当 sel=2'b10 或 2'b11 时 out 保持 → Latch
endcase
end
// 正确: 添加 default
always @(*) begin
case (sel)
2'b00: out = a;
2'b01: out = b;
default: out = 0; // ← 消除 Latch
endcase
end
十一、Git 版本代码演进
11.1 B3 相关 Commits
| Commit | 说明 | 核心变化 |
|---|---|---|
4637c62 |
feat: yosys synthesis Fmax=350MHz | 完成首次综合,评估最高频率 |
fab3c15 |
yosys-sta | 完善 STA 流程,添加 iEDA 时序分析 |
11.2 4637c62 — 首次综合
主要工作:
- 集成 yosys-sta 工具链
- 编写
npc/top.sdc时序约束文件 - NPC Makefile 添加
make syn目标 - 处理综合报错(排除 DPI-C 等不可综合代码)
- 获得 Fmax ≈ 350MHz 的评估结果
关键修改:
# npc/Makefile 中新增:
SYN_TOP = top
syn:
@echo "Synthesis NPC using yosys-sta"
cd $(NPC_HOME)/../yosys-sta && make sta \
DESIGN=$(SYN_TOP) \
SDC_FILE=$(NPC_HOME)/top.sdc \
RTL_FILES="..." \
CLK_FREQ_MHZ=100 \
INCLUDE_PATH=$(NPC_HOME)/vsrc/include
11.3 fab3c15 — STA 完善
- 添加 iEDA 扇出优化步骤
- 获得更准确的时序分析结果
- 生成完整的时序报告(WNS/TNS/路径信息)
十二、Yosys 综合脚本详解
12.1 ABC 优化脚本
Yosys 内部使用 ABC (A System for Sequential Synthesis and Verification) 做技术映射:
# yosys.tcl 中的 ABC 优化脚本:
set abc_script "+strash;ifraig;retime,-D,{D},-M,6;strash;dch,-f;map,-p,-M,1,{D},-f;topo;dnsize;buffer,-p;upsize;"
各步骤含义:
| 命令 | 功能 |
|---|---|
strash |
结构化哈希 (AND-Inverter Graph 规范化) |
ifraig |
功能等价对消减 |
retime,-D,{D},-M,6 |
寄存器重定时 (时钟周期=D, 最多移动6级) |
dch,-f |
AIG 选择 (DAG-aware rewriting) |
map,-p,-M,1,{D},-f |
技术映射 (延迟优化, 时钟约束=D) |
topo |
拓扑排序 |
dnsize |
缩小不必要的大单元 |
buffer,-p |
插入缓冲修复时序 |
upsize |
增大关键路径上的单元 |
12.2 关键综合命令
# 推断寄存器和高层结构
synth -top $DESIGN
# 触发器映射: 将 always @(posedge clk) 中的寄存器映射到库中的 DFF
dfflibmap -liberty $MERGED_LIB_FILE
# 组合逻辑映射: 在延迟约束下将逻辑映射到标准单元
abc -D [expr $CLK_PERIOD_NS * 1000] -liberty $MERGED_LIB_FILE
# -D 参数: 目标延迟 (皮秒), ABC 会尝试让所有路径满足此约束
# 100 MHz: -D 10000 (10ns = 10000ps)
十三、iEDA 工具详解
13.1 iNO — 网表优化
iNO (iEDA Network Optimization) 的主要功能是修复扇出违例:
问题: 某个信号驱动太多负载 (扇出过高)
clk_gate_out ──┬──▶ DFF_1
├──▶ DFF_2
├──▶ DFF_3
├──▶ ...
└──▶ DFF_100 ← 扇出=100, 转换时间太慢!
解决: 插入缓冲树
clk_gate_out ──▶ BUF ──┬──▶ DFF_1~DFF_50
└──▶ BUF ──┬──▶ DFF_51~DFF_100
13.2 iSTA — 静态时序分析
iSTA (iEDA Static Timing Analysis) 的工作流程:
# sta.tcl 脚本:
set_design_workspace $RESULT_DIR # 设置工作目录
read_netlist $NETLIST_V # 读入优化后的网表
read_liberty $LIB_FILES # 读入单元库 (.lib)
link_design $DESIGN # 链接设计
read_sdc $SDC_FILE # 读入时序约束
report_timing # 生成时序报告
输出报告内容:
- Setup 检查: 数据到达时间 vs 时钟约束
- Hold 检查: 数据保持时间 vs 最短路径
- WNS/TNS: 全局时序质量指标
- 关键路径: 延迟最长的 N 条路径的详细信息
13.3 iSTA 报告解读参考
iSTA 报告解读可参考 B站视频 BV1a14y1B7uz
报告中的关键信息:
- WNS (Worst Negative Slack): 负值 = 时序违例,正值 = 有余量
- TNS (Total Negative Slack): 所有违例路径的 slack 之和
- 关键路径: 显示从哪个寄存器到哪个寄存器,经过了哪些组合逻辑
- 电容/扇出/转换时间违例: 物理实现层面的约束检查
十四、从时序分析到设计优化的闭环
14.1 迭代优化流程
┌─────────────────────────────────────────────────────┐
│ │
│ RTL 修改 ──▶ make syn ──▶ 查看报告 ──▶ 分析瓶颈 │
│ ↑ │ │
│ └───────────── 优化设计 ◀───────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
14.2 优化决策树
时序违例 (WNS < 0)?
├── YES: 查看关键路径
│ ├── ALU 运算路径过长?
│ │ └── 选项: 多周期 ALU / 流水线化 ALU / 换用更快的加法器结构
│ ├── AXI 地址解码路径过长?
│ │ └── 选项: 使能 SPILL_AW/SPILL_AR (项目已使能)
│ ├── 寄存器堆读取 + 组合逻辑?
│ │ └── 选项: 在 IDU→EXU 之间插入流水级
│ └── 控制逻辑路径?
│ └── 选项: 简化控制逻辑 / 使用更少的 MUX 层级
└── NO (有余量):
├── 余量很大? → 可以尝试更高频率约束
└── 余量适中? → 当前设计可以接受
14.3 本项目的优化点
| 优化位置 | 手段 | 效果 |
|---|---|---|
| AXI Demux AW/AR | Spill Register | 切断地址解码关键路径 |
| Valid/Ready 握手 | 弹性流水线 | 支持后续插入流水级 |
| IFU→ICache | Demux 分路 | ICache 命中时绕过长路径 |
defines.vh 的 SYNTHESIS 宏 |
排除不可综合代码 | 综合能正确完成 |
十五、与其他阶段的关系
15.1 B3 的分析结果如何指导 B4 (Cache)
- STA 告诉你 IFU 取指路径有多长 → ICache 命中时应该在 1 cycle 内返回
- 如果 ICache 的 tag 比较 + 数据读取路径太长 → 需要拆分为 2 cycle(先比较 tag,再读数据)
- ICache 的面积预估(通过综合看 SRAM/DFF 数量)
15.2 B3 的分析结果如何指导 B5 (流水线)
- 识别出最长路径 → 确定在哪里切割流水级
- 如果 IDU→EXU 路径长 → 在中间加流水寄存器
- 如果 EXU→BRU (分支) 路径长 → 分支预测才有意义
15.3 B3 为流片准备了什么
- 验证设计在目标工艺下可行
- 确认没有 Latch 推断、组合环等致命问题
- 获得面积估算(决定能否放入目标芯片)
- 为后端布局布线提供约束基础
十六、PA 讲义相关思考题
Q: 为什么仿真正确的设计综合后可能有时序违例?
回答:
仿真只验证功能正确性(逻辑行为),不关心延迟。仿真中所有组合逻辑都在"零时间"完成。但真实硬件中每个门都有传播延迟,如果一条路径上的门太多,信号无法在一个时钟周期内从源寄存器传到目标寄存器,就会产生时序违例。
类比:逻辑上你的代码是对的(单元测试通过),但在规定时间内跑不完(性能不达标)。
Q: Spill Register 如何在不改变功能的前提下改善时序?
回答:
Spill Register 本质是在 Valid/Ready 握手路径中插入一个 2-entry 缓冲:
- 上游发数据时,Spill Register 接收并存储(ready_o=1 表示可接收)
- 下游取数据时,Spill Register 输出存储的数据(valid_o=1 表示有数据)
- 组合路径被完全切断:上游的 valid/data 到下游的 valid/data 中间有寄存器
功能上等价于直连(只增加 1 cycle 延迟),但时序上路径长度减半。
Q: 为什么选择 nangate45 工艺而不是真实工艺?
回答:
- nangate45 是开源免费的学术工艺库,任何人都可以使用
- 真实工艺库(如 TSMC 28nm)受 NDA 保护,不能公开
- 一生一芯项目需要开源可复现的评估流程
- nangate45 的绝对数字(延迟/面积)不代表流片结果,但相对比较有意义
- 例如:优化后 Fmax 从 200MHz 提升到 350MHz → 说明优化有效,即使实际工艺下数字不同
Q: 100 MHz 的时钟约束是如何选择的?
回答:
- 100 MHz 是一生一芯项目的基础目标频率
- 对于流片验证,100MHz 已经足够运行演示程序
- 约束太松(10MHz)→ 综合器不会优化,看不出问题
- 约束太紧(1GHz)→ 大量违例,综合结果不可用
- 先用 100MHz 验证可行性,再逐步提高约束看极限
十七、常见问题与解决
Q1: make syn 报错: 找不到 yosys
解决: 安装 Yosys
sudo apt install yosys
Q2: make syn 报错: 找不到 iEDA
解决: 初始化 yosys-sta 环境
cd yosys-sta
make init # 下载 iEDA 二进制和 nangate45 工艺库
Q3: 综合报错: 不认识 DPI-C 语法
原因: 综合器无法处理仿真专用的 DPI-C 调用。
解决: 使用条件编译 ifndef SYNTHESIS 包围所有 DPI-C 代码。
Q4: 综合报告大量 Latch 警告
原因: always @(*) 块中的 case/if 缺少完整覆盖。
解决: 添加 default 分支,确保所有信号在所有分支都有赋值。
Q5: WNS 为负值(时序违例)
分析步骤:
- 查看
top.rpt中的关键路径 - 确定瓶颈在哪个模块(ALU? 地址解码? 控制逻辑?)
- 考虑插入 Spill Register 或简化组合逻辑
- 如果违例很大(如 -5ns),可能需要架构级修改
Q6: 面积过大
原因可能:
- 大型 MUX 树(如 LSU 的字节选择逻辑)
- 冗余逻辑未优化
- Yosys 的
opt步骤没有充分运行
解决: 检查 synth_stat.txt 中哪类单元占比最大。
十八、学习建议
- 先跑通 GCD 样例 — 在
yosys-sta/example/目录下验证工具链正常 - 再综合 NPC —
make syn获得自己设计的报告 - 仔细阅读
synth_check.txt— 确认没有致命警告 - 理解时序报告 — 找到关键路径,画出路径图
- 尝试不同约束 — 改变
CLK_FREQ_MHZ看 WNS 如何变化 - 对比优化前后 — 使能/禁用 Spill Register 看面积和时序的变化
- 参考 iSTA 视频 — B站 BV1a14y1B7uz
十九、RTL 源文件清单
19.1 B3 阶段涉及的文件
yosys-sta/ # 综合 + STA 工具链
├── Makefile # 综合流程管理
├── scripts/
│ ├── yosys.tcl # Yosys 综合脚本
│ ├── sta.tcl # iSTA 时序分析脚本
│ ├── fix-fanout.tcl # iNO 扇出优化脚本
│ └── fix-fanout.json # 扇出优化配置
├── nangate45/ # 工艺库 (make init 下载)
│ ├── lib/merged.lib # 标准单元延迟/功耗模型
│ └── verilog/ # 标准单元行为模型
└── result/top-100MHz/ # 综合结果 (运行后生成)
├── top.netlist.syn.v # 综合网表
├── top.netlist.fixed.v # 优化网表
├── top.rpt # 时序报告
├── synth_stat.txt # 面积报告
└── synth_check.txt # 综合检查
npc/
├── Makefile # syn 目标
├── top.sdc # 时序约束文件
└── vsrc/
├── include/defines.vh # SYNTHESIS 宏定义
└── libs/
├── spill_register.sv # Spill Register (时序优化)
└── spill_register_flushable.sv # Flushable 版本
参考资料:
- Yosys 官方文档: https://yosyshq.readthedocs.io/
- iEDA 源码: https://github.com/OSCC-Project/iEDA
- iSTA 报告解读: https://www.bilibili.com/video/BV1a14y1B7uz/?t=1006
- 一生一芯 yosys-sta 初始化: https://ysyx.oscc.cc/slides/resources/scripts/init-yosys-sta.sh
- NanGate 45nm Open Cell Library
- 项目源码: npc/Makefile, npc/top.sdc, yosys-sta/
- PULP Spill Register: https://github.com/pulp-platform/common_cells

浙公网安备 33010602011771号