ASIC到FPGA移植:存储器与Flash适配
一、ASIC SRAM vs FPGA BRAM
移植的第一步是认清差距。ASIC 的编译 SRAM 可以任意定制(深度、位宽、端口数、字节使能粒度),FPGA 的 BRAM 是硬核——大小固定、位宽固定、端口固定。
| 维度 | ASIC 编译 SRAM | Kintex-7 BRAM 36Kb |
|---|---|---|
| 容量 | 任意定制 | 36Kb (可拆为 2×18Kb) |
| 位宽 | 任意 (29bit, 37bit...) | ×1, ×2, ×4, ×9, ×18, ×36 |
| 端口 | 1RW, 1R1W, 2RW | SDP / TDP |
| 字节使能 | 按 bit/byte | 仅 byte (wea[3:0]), TDP 无字节使能 |
| 级联 | 无 | CASCADE 扩展深度 |
| FIFO | 无 | 内建 FIFO 控制器 |
| ECC | 可选 | 每 64bit 附带 8bit |
容量参考:XC7K325T = 445 块 36Kb BRAM ≈ 16Mbit,XC7K410T = 795 块 ≈ 28Mbit。ASIC SRAM 总量不能直接除 36Kb——不同位宽的 SRAM 打包到 BRAM 时有"碎片"(比如 29bit 的 SRAM 需要用 36bit 的 BRAM 去装,7bit 浪费)。
二、BRAM 封装层设计
实际项目用一个薄封装层把 Vivado BMG IP 包装成与原始 SRAM 兼容的接口。这个封装层做了四件事。
2.1 地址译码
当一块 ASIC SRAM 超过单块 BRAM 容量时,用高位地址做片选,映射到多个 BRAM 实例:
addr[15:13] → 3-to-8 译码 → 8 扇区 × 8K×32 = 64K×32
8 个扇区地址空间连续,上层看到的 64K×32 地址空间自动路由到正确的 BRAM 块。
2.2 写使能展宽
Vivado BMG IP 的 ena 和 wea 必须至少持续一个完整时钟周期。但 Flash 状态机产生的 prog 编程脉冲可能只有一拍。解法:脉冲展宽 3 拍。
reg prog_d1, prog_d2;
wire prog_ext = prog | prog_d1 | prog_d2;
always @(posedge clk) begin
prog_d1 <= prog;
prog_d2 <= prog_d1;
end
3 拍足够覆盖任何 BMG IP 的采样窗口。
2.3 输出保持寄存器
BMG IP 在 ena=0 时 douta 输出浮空——仿真表现为 X,硬件为不确定值。封装层必须加一级保持寄存器:
reg [31:0] dout_r;
always @(posedge clk)
if (read | prog)
dout_r <= bram_dout; // 只在读/写时更新,其余周期保持
assign dout = dout_r;
2.4 字节使能转换
Flash 的 mask 信号语义与 BRAM 的 wea 相反:mask=1 表示跳过(不写),wea=1 表示写入。封装层统一取反:
wire [3:0] wea = {prog_ext & (~mask[3]),
prog_ext & (~mask[2]),
prog_ext & (~mask[1]),
prog_ext & (~mask[0])};
三、Flash 语义适配层:本项目的核心改造
前面讲的 BRAM 封装层只是解决了"存储"问题。真正的挑战是——ASIC 的 Flash 有物理编程约束,FPGA 的 BRAM 没有。如果不做语义适配,DSP 固件写到 Flash 上的数据就跟 ASIC 不一致。
3.1 Flash 的三个物理约束
- 1→0 only:编程只能把 bit 从 1 变成 0。要把 0 变回 1,必须先擦除整个扇区
- 擦除全 1:以扇区为单位,擦除后扇区内所有 bit = 1
- 字节 inhibit:如果某字节全为 1(不需要编程),可以跳过写入
这些约束在 ASIC 中由 Flash 宏单元 (BIS_064K29DB) 的物理特性保证。FPGA 的 BRAM 可以任意写——你今天给某个 bit 写 0,明天写 1,BRAM 照单全收。不做适配 = 行为不一致。
3.2 六层架构
flash_semantic_layer_template.v 在 BRAM 之上构建了一个六层栈,逐层模拟 Flash 语义:
⑥ BRAM 薄封装 ← sram1rw_l/h,底层存储
⑤ 擦除控制 ← 地址遍历 + din 全 1 + erase_tail 尾脉冲保护
④ 字节掩码 ← & 归约:全 1 字节 = 跳过
③ AND 掩码 ← Origin & din → 模拟 1→0 编程
② 读-改-写 ← PGS 首拍锁存 Origin_Data
① 读使能生成 ← 常规读 + 编程前强制读
逐层分解:
① 读使能生成
两个来源产生读操作:正常的 READ0~READ3 状态读,以及编程状态 PGS 的首拍强制读(为了锁存 Origin 数据)。
assign read = (state==READ0 || state==READ1 || state==READ2
|| state==READ3) && read_finish_pre;
assign read_beforeProg = (state==PGS) && (wait_cnt==20'd0);
assign New_read = read || read_beforeProg;
② 读-改-写:Origin 锁存
编程前必须知道 Flash 当前内容,否则 AND 掩码没法计算。PGS 首拍的 read_beforeProg 产生 New_read,锁存:
reg [31:0] Origin_DataL, Origin_DataH;
always @(posedge sysclk)
if (New_read) begin
Origin_DataL <= dout[31:0];
Origin_DataH <= dout[63:32];
end
③ AND 掩码:模拟 1→0 物理限制
核心一行就够:din 中为 0 的 bit 拉低,为 1 的 bit 保持原位。
assign Real_ProgDataL = Origin_DataL & din[31:0]; // din=1→保持 / din=0→写0
举例:Origin = 1010_1100,din = 1111_0011(想写后两位为 11)。AND 之后:1010_1100 & 1111_0011 = 1010_0000。后两位 11 中第一个 1 保持了原值,第二个 0 被写为 0。物理 Flash 的行为就是这个结果。
④ 字节掩码
全 1 字节(不需要编程的字节)跳过写入,用 & 归约检测:
assign Real_ProgmaskL = {&Real_ProgDataL[31:24], // 8bit 全 1 → 1(跳过)
&Real_ProgDataL[23:16],
&Real_ProgDataL[15:8],
&Real_ProgDataL[7:0]};
⑤ 擦除控制 + erase_tail 尾脉冲保护
擦除以扇区为单位遍历所有地址,逐地址写全 1。这里有一个调试中定位到的关键 bug:prog_ext 展宽 3 拍意味着擦除完成后,prog_ext 还会多持续 2~3 拍。如果此时 din 恢复为编程数据(非全 1),尾脉冲会把编程数据写入刚擦除干净的地址——"擦完又写脏"。
修复方案:erase_tail 寄存器,从擦除开始直到回到 IDLE 状态之前,din 全部强制为 32'hFFFFFFFF。
reg erase_tail;
always @(posedge sysclk or negedge rst_por_n) begin
if (!rst_n) erase_tail <= 1'b0;
else if (state == IDLE) erase_tail <= 1'b0;
else if (erase_finish) erase_tail <= 1'b1;
end
// ERASE → ERCV → IDLE 全链路保护
assign din_l32 = (erase_finish | erase_tail) ? 32'hFFFFFFFF : Real_ProgDataL;
⑥ BRAM 薄封装
最终落到 Vivado BMG IP:18 个 IP 实例(8 扇区 × 2 高低 + 2 OTP),SDP 模式,WRITE_FIRST,DOA_REG=1。
四、关键设计决策速查
| 决策 | 选择 | 原因 |
|---|---|---|
prog 脉冲 |
展宽 3 拍 | BMG IP 需要至少 1 完整周期 |
dout 防 X |
保持寄存器 | BMG ena=0 时输出浮空 |
| 扇区编码 | addr[15:13] | 天然 3-to-8 译码 |
| OTP 区分 | nvr 信号 |
独立地址空间 |
| 字节使能 | wea = prog & ~mask |
Flash mask 与 BRAM wea 语义相反 |
| 擦除保护 | erase_tail |
尾脉冲防止编程数据污染 |
| 双路径 | ifdef fpga_vision |
ASIC 路径保留,仿真对比用 |
五、代码改动原则
Flash 语义适配层遵循只增不改:ASIC 原始逻辑一字不动,只在 FPGA 分支新增必要的保护和 Tie-off。
ASIC 原始逻辑 (read / Origin 锁存 / AND 掩码 / 字节掩码 / 擦除控制)
→ 完全保留
FPGA 新增:
- erase_tail 寄存器 + 保护逻辑
- localparam 状态编码 (IDLE / ERCV)
- 顶层端口 Tie-off (osc_*, adc_*, secure_* 等)
- 内部信号 Tie-off (地址=0, 数据=0, 状态=IDLE, 计数=0)
仿真时 testbench 通过 force 覆盖 Tie-off 信号,综合时这些常数值被优化掉,不消耗逻辑。
六、总结
存储器替换有三个层次:
- SRAM → BRAM:封装层解决,机械操作,做好地址译码、脉冲展宽、输出保持、字节使能四件事
- Flash → BRAM + 语义适配层:本项目核心改造,六层栈逐层模拟物理约束,关键技术是 AND 掩码 + 擦除遍历 + erase_tail 尾脉冲保护
- 大容量 Flash / eMMC → 外部存储 + 控制器:需要 FMC 子卡配合,后面单独成篇
一句话:FPGA 的 BRAM 只是砖头,Flash 语义适配层才是把砖头砌成大楼的那层水泥。
浙公网安备 33010602011771号