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_t、init_ss_t、init_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. 性能优化与堆区调整
- 原因:关闭
FAST_FLASH 后,XIP 状态机模拟 SPI 时序,取指极慢。
- 解决:在
spi_top_apb.v 中定义 FAST_FLASH 宏,启用 DPI-C 快速模式。
8.2 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_VECTOR 从 0x20000000 改为 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 奠定了坚实基础。