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 的 enawea 必须至少持续一个完整时钟周期。但 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=0douta 输出浮空——仿真表现为 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 信号,综合时这些常数值被优化掉,不消耗逻辑。


六、总结

存储器替换有三个层次:

  1. SRAM → BRAM:封装层解决,机械操作,做好地址译码、脉冲展宽、输出保持、字节使能四件事
  2. Flash → BRAM + 语义适配层:本项目核心改造,六层栈逐层模拟物理约束,关键技术是 AND 掩码 + 擦除遍历 + erase_tail 尾脉冲保护
  3. 大容量 Flash / eMMC → 外部存储 + 控制器:需要 FMC 子卡配合,后面单独成篇

一句话:FPGA 的 BRAM 只是砖头,Flash 语义适配层才是把砖头砌成大楼的那层水泥。

posted @ 2026-07-31 16:19  小民的硬件笔记  阅读(0)  评论(0)    收藏  举报