ASIC到FPGA移植:10步流程实战
全流程总览
Step 0 Step 1 Step 2 Step 3 Step 4
设计盘点 → 存储器替换 → 时钟替换 → 移除 DFT → 模拟 IP Tie-off
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
grep 盘点 BRAM 封装 PLL 配置 全部 Tie-off osc/adc 固定
Step 5 Step 6 Step 7 Step 8 Step 9 Step 10
复位同步 → IO Pad → CDC 分析 → 时序约束 → 综合实现 → 板级调试
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
每域同步 Pad 宏移除 异步 FIFO create_clock Vivado ILA 上板
set_false_path bitstream 14/14 PASS
Step 0:设计盘点
在改任何代码之前,先把 ASIC 设计的家底摸清。
盘点六项清单:
| 类别 | 检查什么 | 怎么查 |
|---|---|---|
| IP 清单 | 第三方 IP / 自研 IP / 模拟 IP | RTL 文件列表 + 例化层次 |
| 存储器 | SRAM 类型、深度、位宽、端口 | grep -r "BIS_|SRAM" *.v |
| 时钟域 | 时钟源、频率、门控结构 | grep -r "posedge|clock|clk" *.v |
| 复位域 | 复位信号、同步/异步 | grep -r "negedge|rst|reset" *.v |
| I/O | 顶层端口方向、位宽、电压域 | 顶层模块端口列表 |
| DFT | 扫描链 / BIST / 测试模式 | grep -r "scan|bist|test_en" *.v |
盘点的产出是一张对照表——ASIC 侧有什么 vs FPGA 侧用什么替代。以 Flash 子系统为例:
ASIC 侧:
存储器: BIS_064K29DB ×2 (64K×29bit 双通道)
时钟域: sysclk (100MHz)
模拟口: vpp (inout, 高压编程)
DFT: TCK/TDI/TDO/TESTEN/STROBE
FPGA 侧 (NE-7K 载板 + FMCH-FLASH 子卡):
目标器件: XC7K325T-2FFG900
载板时钟: 200MHz DDR3 sys + 74.25MHz + 135MHz LVDS
子卡: NOR Flash 1Gbit + SPI Flash 512Mbit + eMMC 8GB
接口: FMC HPC1, Bank 15/16/17/18, VADJ=3.3V
Step 1:存储器替换
将所有 ASIC 编译 SRAM 实例替换为 FPGA BRAM 封装层。
位宽向上取整:ASIC SRAM 位宽非 8/16/32 倍数时向上取整,多出的 bit 写 0、读忽略。
`ifdef fpga_vision
// BIS_064K29DB: 64K×29bit → BRAM 64K×32bit
sram1rw_64k32 #(.AWIDTH(16), .DWIDTH(32)) u_bram (
.clk (sysclk),
.din ({3'b0, din[28:0]}), // 29→32bit, 高位补 0
.dout (dout) // 读回 32bit, 忽略高 3bit
);
`else
BIS_064K29DB u_bist (...); // ASIC 原始
`endif
Flash 语义适配层的六层架构详见上一篇文章。
Step 2:时钟生成器替换
将 ASIC PLL/OSC 时钟树替换为 FPGA PLL + 载板时钟源。
NE-7K 载板实际时钟方案——在顶层新增 XCLKIN_P/N 差分时钟口:
XCLKIN_P/N (差分, FPGA 新增顶层端口)
↓
顶层 PLL → 30MHz 参考时钟
↓
内部 OSC 模块被 FPGA PLL 替代
→ 10MHz / 15MHz / 20MHz / 30MHz / 40MHz (五路)
→ osc_*_trim = 4'h8 (Tie-off 中点)
创建 PLL IP 核:Vivado IP Catalog → Clocking Wizard → PLLE2_ADV,配置输入频率和五路输出分频比。
PLL 选型:没有动态移相需求(无 DDR3)→ 选 PLL(低功耗、配置简单);有 DDR3/源同步接口 → 选 MMCM(动态相位校准)。
Step 3:移除 DFT / BIST / SCAN
红线——全部 Tie-off 或 `ifdef 排除。
`ifdef fpga_vision
assign scan_enable = 1'b0;
assign scan_mode = 1'b0;
assign test_mode = 1'b0;
// MBIST 控制器整块不例化
// 扫描 MUX 直接选功能路径
// assign sig = scan_mode ? scan_in : func_sig; → assign sig = func_sig;
`endif
综合后检查 synth.log,确认无 DFT 相关 warning。Vivado 中通过 synth_design -verilog_define fpga_vision 启用宏。
Step 4:处理模拟 IP
| 模拟 IP | 处理方式 | 示例 |
|---|---|---|
| Flash VPP | 拉低 | assign vpp = 1'b0 |
| OSC trim | Tie-off 中点 | 4'h8 |
| ADC trim | Tie-off 中点 | 16'h8000 |
| CSM 安全模块 | 旁路 | assign secure = 1'b0 |
Step 5:复位同步适配
每个时钟域一个独立的异步复位同步释放电路:
module reset_sync (
input wire clk,
input wire rst_n_async,
output wire rst_n_sync
);
reg [1:0] rst_meta;
always @(posedge clk, negedge rst_n_async) begin
if (!rst_n_async)
rst_meta <= 2'b0;
else
rst_meta <= {rst_meta[0], 1'b1};
end
assign rst_n_sync = rst_meta[1];
endmodule
注意事项:多个复位源(POR、WDT、SW reset)在 FPGA 中合并为一个主复位;禁止在 RTL 中使用 #1 延迟复位(ASIC 仿真用的物理延迟模型,FPGA 不综合)。
Step 6:I/O Pad 替换与引脚分配
移除 ASIC IO pad 宏单元,FPGA IOB 直连:
`ifdef fpga_vision
assign chip_io_pad = internal_output; // 输出直连
assign internal_input = chip_io_pad; // 输入直连
`else
PAD_IN u_pad_in (.PAD(pad), .Y(internal)); // ASIC pad
PAD_OUT u_pad_out (.PAD(pad), .A(internal));
`endif
引脚分配三条原则:
- 时钟 → 专用时钟引脚 (MRCC/SRCC)
- Bank 电压必须匹配 IO 标准(FMC VADJ=3.3V → LVCMOS33)
- JTAG → FPGA 专用 JTAG 引脚,不与 ASIC TAP 共用
Step 7:CDC 跨时钟域分析
所有跨时钟域路径必须识别并同步。五种场景的处理方式:
| 跨域类型 | 同步策略 | 验证方式 |
|---|---|---|
| 单 bit 控制信号 | 2 级同步器 | 仿真 |
| 多 bit 数据总线 | 异步 FIFO(格雷码指针) | 仿真 + 时序分析 |
| 慢 → 快时钟域 | 2 级同步器即可 | 确认无漏采样 |
| 快 → 慢时钟域 | 展宽 + 同步器 | 仿真检查展宽宽度 |
| 复位信号 | 异步复位同步释放 | Step 5 电路 |
异步 FIFO 直接用 Vivado FIFO Generator IP——自己写格雷码指针容易出 bug,IP 核经过充分验证。
Step 8:时序约束 (.xdc) 创建
一份完整约束至少覆盖这五类:
# 1. 时钟定义
create_clock -period 33.333 -name xclkin [get_ports {xclkin_p xclkin_n}]
create_generated_clock -name clk_30m -source [get_ports xclkin_p] \
-divide_by 1 [get_pins clk_pll/clk_out1]
# 2. 异步接口豁免 (Flash 为异步接口)
set_false_path -from [get_ports {nor_*}] -to [get_ports {nor_*}]
set_max_delay -from [get_ports {nor_d[*]}] -to [get_cells *] 50.000
# 3. FMC 子卡异步信号
set_false_path -from [get_ports {spi_*}] -to [get_ports {spi_*}]
# 4. 复位路径例外 (复位同步器第一级为亚稳态容忍)
set_false_path -through [get_pins reset_sync/*/rst_meta[*]]
# 5. 时钟组 (告诉工具哪些时钟是异步的)
set_clock_groups -asynchronous \
-group [get_clocks xclkin] \
-group [get_clocks clk_30m] \
-group [get_clocks {clk_10m clk_15m clk_20m clk_40m}]
Step 9:综合与实现
标准 Tcl 流程:
# 创建项目
create_project -part xc7k325tffg900-2
add_files -scan [glob rtl/*.v]
add_files -fileset constrs_1 constrs/qmx201e_top.xdc
add_files -fileset constrs_1 constrs/FLASH.xdc
# 综合
synth_design -top qmx201e_top -flatten_hierarchy rebuilt
write_checkpoint -force synth.dcp
# 实现
opt_design
place_design
phys_opt_design # 物理优化
route_design
write_checkpoint -force impl.dcp
# Bitstream
write_bitstream -force qmx201e.bit
综合后常见问题速查:
| 问题 | 原因 | 解法 |
|---|---|---|
| 面积过大 | 门控时钟未转换 | 检查是否还有 @(posedge gated_clk) |
| 大量 Latch | case/default 不完整 | 补全 case default 和 else 分支 |
| BRAM 推断失败 | 地址译码不满足模板 | 改用 BMG IP 显式例化 |
| 三态冲突 | assign ... = 'bz |
FPGA 内部不支持三态,改用 MUX |
| DSP 推断失败 | 写法不符合 DSP 模板 | 检查 * / + 运算符写法 |
Step 10:板级调试
bitstream 下载到 NE-7K 载板后:
- 上电确认:电源轨正常(载板 TI UCD9090 管理)、时钟有输出(示波器测 BUFG 输出)
- ILA 探针:例化 Vivado ILA IP 核,抓关键状态机、Flash 读写握手、BRAM 接口
- 运行回归测试:14 个 test case 全部 PASS → 移植完成
总结
10 步流程按依赖关系排列,前面是后面的前提:
前 5 步是"修 RTL":盘点 → 存储器 → 时钟 → DFT → 模拟 IP
↓
后 5 步是"收口":复位 → IO → CDC → 约束 → 综合上板
三条经验:
- 先通后优——先跑通全量综合通过、仿真 PASS,再管时序收敛
- 只增不改——ASIC 原始代码一字不动,FPGA 逻辑全部加在
`ifdef分支里 - 保留仿真对照——ASIC 路径和 FPGA 路径在同一个 testbench 中对比运行,行为差异一目了然
本系列三篇覆盖了 ASIC→FPGA 移植从认知、存储到实操的完整链路。第一篇讲 Why(为什么要改),第二篇讲 What(具体怎么适配 Flash),本篇讲 How(操作的顺序和命令)。
浙公网安备 33010602011771号