可重复编程的非易失存储器

1. 背景:为什么需要可重复编程的非易失存储器?

1.1 MROM 的局限性

  • 掩膜 ROM(MROM) 的内容在芯片流片时即固定,无法在芯片制造后修改。
  • 成本高昂:重新流片更换程序需要数百万人民币的掩膜费用,周期长达 3~6 个月。
  • 可编程性极弱:仅支持制造时编程,无法适应软件迭代和 bug 修复。

1.2 Flash 存储器的优势

  • 电可擦除可编程:通过施加高压可擦除或写入数据,内容在断电后保持。
  • 低成本可更新:用户可通过烧录器(如 CH341A)或 U 盘等方式轻松更新固件。
  • 大容量:典型 SPI Flash(如 W25Q128JV)提供 16MB 存储空间,远大于 MROM 的 4KB。
  • 支持 XIP(eXecute In Place):CPU 可直接从 Flash 取指执行,无需先搬运到 RAM。

2. Flash 存储单元物理结构

2.1 浮栅晶体管

  • Flash 的基本存储单元是 浮栅场效应管(Floating Gate FET),具有 控制栅极浮置栅极
  • 浮置栅极 被高纯度二氧化硅绝缘层包裹,可存储电荷长达 10 年以上。
  • 写入(Program):通过热电子注入或 F-N 隧道效应,将电子注入浮栅,使阈值电压升高,代表逻辑 “0”。
  • 擦除(Erase):通过 F-N 隧道效应将电子拉出浮栅,阈值电压降低,代表逻辑 “1”。
  • 读取(Read):施加参考电压,检测晶体管是否导通,非破坏性。

2.2 NOR 与 NAND 架构

类型 连接方式 读取速度 随机访问 典型应用
NOR Flash 单元并联 支持(字节级) 程序代码存储、XIP
NAND Flash 单元串联 不支持(页/块访问) 大容量数据存储(SSD、U盘)

2.3 存储层次(以 W25Q128JV 为例)

  • 整颗芯片:16MB(24 根地址线)。
  • 块(Block):64KB × 256 个。
  • 扇区(Sector):4KB × 4096 个,最小擦除单位
  • 页(Page):256B × 65536 个,最小写入单位
  • 字节(Byte)最小读取单位,支持随机读取。

2.4 关键特性

  • 先擦后写:Flash 不能将 “0” 直接改写为 “1”,必须先擦除整个扇区(全部变为 1)再编程。
  • P/E 周期限制:擦写寿命有限(SLC ~10 万次,MLC ~1 万次)。
  • 命令/地址/数据协议:外部通过 SPI 命令(如 0x03 读,0x02 页编程)控制。

3. SPI 总线协议与硬件连接

3.1 SPI 信号线

信号 方向 说明
SCK Master → Slave 时钟,由 Master 产生
SS Master → Slave 片选,低电平有效
MOSI Master → Slave Master 输出,Slave 输入
MISO Slave → Master Slave 输出,Master 输入

3.2 SPI 通信参数

  • 时钟极性(CPOL):空闲时 SCK 电平(0=低,1=高)。
  • 时钟相位(CPHA):数据采样边沿(0=上升沿,1=下降沿)。
  • 位序:MSB First 或 LSB First。
  • 帧长度(CHAR_LEN):一次传输的位数(如 8/16/64 位)。

3.3 SPI Master 的双重身份

  • 作为 APB/AXI 从机:接收 CPU 的寄存器访问(地址 0x1000_1000 起)。
  • 作为 SPI 主机:驱动物理 SPI 引脚与 Flash 通信。

4. ysyxSoC 中的 Flash 硬件实现

4.1 SPI 控制器顶层模块 spi_top_apb.v

  • 功能:将 APB 总线请求转换为 SPI 时序,支持普通 SPI 驱动和 XIP 模式。
  • 地址译码
    • 0x1000_1000~0x1000_1fff:SPI 控制器寄存器,供软件驱动访问。
    • 0x3000_0000~0x3fff_ffff:Flash 存储空间映射,由 XIP 硬件状态机自动处理。
  • XIP 状态机
    • init_div_tinit_ss_tinit_ctrl_t:上电自动初始化 SPI 配置(时钟分频、片选、控制字)。
    • idle_t:等待 CPU 对 Flash 空间的读请求。
    • write_cmd_t:自动将 0x03(读命令)+ 24 位地址写入 SPI 发送 FIFO。
    • start_trans_t:触发 SPI 传输(写 GO_BUSY 位)。
    • wait_trans_t:等待 spi_irq_out 中断信号(传输完成)。
    • read_data_t:从 SPI 接收 FIFO 读取 32 位数据,通过 APB 返回给 CPU。

4.2 FAST_FLASH 宏(仿真加速)

  • 定义该宏:跳过真实 SPI 时序,通过 DPI-C 调用 C 函数 flash_read() 查表返回数据,仿真速度极快,适合运行 Coremark 等大型程序。
  • 注释该宏:启用真实硬件状态机,SPI 引脚翻转,仿真速度大幅降低,但时序真实。

4.3 中断等待 vs 轮询

  • 代码中 wait_trans_t 依赖 spi_irq_out 中断信号,而非轮询状态寄存器。
  • 优点:总线空闲、功耗低、响应快速。
  • 初始化时 spi_ctrl_ie = 1'b1 使能中断。

5. 软件驱动:通过 SPI 访问 Flash

5.1 SPI 初始化(init_spi

void init_spi(uint32_t spi_clock, uint8_t spi_ss, uint8_t char_len,
              uint8_t tx_neg, uint8_t rx_neg, uint8_t lsb) {
    // 配置除数寄存器(SPI_CLK_DIV)
    uint32_t divisor = SYS_CLK / (2 * spi_clock) - 1;
    *(volatile uint32_t *)(SPI_BASE + SPI_DIV) = divisor;
    // 配置片选寄存器
    *(volatile uint32_t *)(SPI_BASE + SPI_SS) = spi_ss;
    // 配置控制寄存器(CHAR_LEN, TX_NEG, RX_NEG, LSB, IE, ASS)
    uint32_t ctrl = (char_len << 16) | (tx_neg << 14) | (rx_neg << 13) |
                    (lsb << 12) | (1 << 8) | (1 << 7); // IE=1, ASS=1
    *(volatile uint32_t *)(SPI_BASE + SPI_CTRL) = ctrl;
}

5.2 软件版 flash_read(驱动 SPI master)

uint32_t flash_read(uint32_t addr) {
    uint8_t tx_buf[8] = {0x03, (addr>>16)&0xFF, (addr>>8)&0xFF, addr&0xFF};
    spi_tx(tx_buf, 64);      // 发送 64 位(命令+地址+4字节dummy)
    spi_tx_start();          // 触发传输
    spi_rx(rx_buf, 64);      // 阻塞等待完成
    // 提取后 4 字节(Flash 返回的数据)
    return (rx_buf[4]<<24) | (rx_buf[5]<<16) | (rx_buf[6]<<8) | rx_buf[7];
}

5.3 直接指针 XIP 读取(硬件自动处理)

uint32_t data = *(volatile uint32_t *)0x30001000;  // 触发硬件 XIP 状态机

6. 链接脚本与程序布局

6.1 传统 MROM 模式(代码在 MROM,数据在 SRAM)

MROM (rx) : ORIGIN = 0x20000000, LENGTH = 4K
SRAM (rw) : ORIGIN = 0x0f000000, LENGTH = 8K
SECTIONS {
    .text : { *(.text) } > MROM
    .rodata : { *(.rodata) } > MROM
    .data : { *(.data) } > SRAM AT> MROM
    .bss : { *(.bss) } > SRAM
}

6.2 XIP 模式(代码在 Flash,数据仍在 SRAM)

FLASH (rx) : ORIGIN = 0x30000000, LENGTH = 16M
SRAM (rw) : ORIGIN = 0x0f000000, LENGTH = 8K
SECTIONS {
    .text : { *(.text) } > FLASH      /* 直接运行在 Flash */
    .rodata : { *(.rodata) } > FLASH
    .data : { *(.data) } > SRAM AT> FLASH   /* 初始值在 Flash,运行在 SRAM */
    .bss : { *(.bss) } > SRAM
    _sdata_lma = LOADADDR(.data);      /* 供启动代码搬运数据 */
    _stack_top = ORIGIN(SRAM) + LENGTH(SRAM);
}

6.3 启动代码(start.S)必须执行

  • 设置栈指针 sp = _stack_top
  • 清空 BSS 段。
  • _sdata_lma(Flash)搬运 .data_sdata(SRAM)。
  • 跳转 main

7. 仿真环境设置

7.1 sim_main.cpp 中的 Flash 数组

#define FLASH_SIZE (16 * 1024 * 1024)
static uint8_t flash_data[FLASH_SIZE];

void init_flash() {
    FILE *fp = fopen("program.bin", "rb");
    fread(flash_data, 1, file_size, fp);
    fclose(fp);
}

7.2 DPI-C 回调(FAST_FLASH 模式)

extern "C" void flash_read(int32_t addr, int32_t *data) {
    uint32_t offset = addr & 0x00FFFFFF;
    *data = (flash_data[offset] << 24) | (flash_data[offset+1] << 16) |
            (flash_data[offset+2] << 8) | flash_data[offset+3];
}

8. 性能优化与堆区调整

8.1 Coremark 运行缓慢

  • 原因:关闭 FAST_FLASH 后,XIP 状态机模拟 SPI 时序,取指极慢。
  • 解决:在 spi_top_apb.v定义 FAST_FLASH 宏,启用 DPI-C 快速模式。

8.2 Microbench 堆区不足

  • 原因:SRAM 仅 8KB,栈和堆共享,microbench 某些子项(如 malloc)需要大块堆。
  • 解决:在链接脚本中扩大堆区,压缩栈空间:
    _heap_start = _ebss;
    _heap_end = ORIGIN(SRAM) + LENGTH(SRAM) - 512;  // 仅预留 512 字节栈
    
    或修改 microbench 源码跳过内存密集型测试。

9. 学号 CSR 的添加

9.1 硬件修改(csrfile.v

// mvendorid: "ysyx" ASCII
stdreg #(.WIDTH(32), .RESET_VAL(32'h79737978)) reg_mvendorid (...);
// marchid: 学号十进制(如 22068888 -> 0x0150BE98)
stdreg #(.WIDTH(32), .RESET_VAL(32'h0150BE98)) reg_marchid (...);

9.2 软件读取

static inline uint32_t csr_read(uint32_t csr) {
    uint32_t val;
    asm volatile ("csrr %0, %1" : "=r"(val) : "i"(csr));
    return val;
}
void print_id() {
    uint32_t vendor = csr_read(0xF11);
    uint32_t arch = csr_read(0xF12);
    printf("ysyx %d\n", arch);  // 输出 "ysyx 22068888"
}

10. 从 Flash 启动(替代 MROM)

10.1 修改复位 PC

  • 在 NPC 顶层将 RESET_VECTOR0x20000000 改为 0x30000000

10.2 程序烧录

  • program.bin 放入 flash_data[0] 起始位置。
  • 仿真环境 init_flash() 加载该文件。

10.3 验证

  • 仿真复位后 PC = 0x30000000,硬件 XIP 状态机自动取指。
  • 若输出正常字符或运行 Coremark,即成功。

11. 常见问题与避坑指南

问题 原因 解决方案
仿真极慢 关闭了 FAST_FLASH 定义 FAST_FLASH
程序跑飞 栈溢出或数据段未搬运 检查 start.S.data 搬运和栈顶设置
printf 不输出 未初始化 UART main 开头调用 init_uart(115200)
Flash 读回值错误 字节序或命令格式错误 确认 flash_read 按 MSB 组合,地址低 24 位提取
XIP 取指死锁 在 Flash 中执行软件 SPI 驱动 使用指针直接访问,勿混用 XIP 和软件驱动
DiffTest 失败 NEMU 未同步 mvendorid/marchid 在 NEMU CSR 中添加相同值

12. 总结

通过引入 SPI Flash,ysyxSoC 实现了:

  • 大容量程序存储(16MB),远超 MROM 的 4KB。
  • 可重复编程,支持快速迭代和固件更新。
  • XIP 就地执行,无需搬运即可运行,降低启动延迟。
  • 硬件状态机自动化,软件透明访问 Flash 映射地址。
  • 中断等待机制,降低功耗,提升总线利用率。

同时,通过 FAST_FLASH 宏平衡仿真速度,通过链接脚本分离代码与数据区,通过 CSR 标识芯片身份,ysyxSoC 已具备“真实芯片级”的启动和存储管理能力。这一系列设计为后续运行 Coremark、RTOS 乃至 Linux 奠定了坚实基础。

posted @ 2026-07-16 17:10  mo686  阅读(8)  评论(0)    收藏  举报