249 lines
18 KiB
Markdown
249 lines
18 KiB
Markdown
# sfc测试例程说明文档
|
||
|
||
本测试程序用于测试sfc基础功能,主要包括通过sfc对flash执行寄存器读写,数据擦除和读写等例程。
|
||
|
||
## sfc简介
|
||
|
||
sfc全程为spi flash controller,从属于EMC(external memory controller)模块,是芯片内部封装的用于访问外部flash的功能模块,简化了软件访问外部flash的逻辑和时序,其主要特点如下:
|
||
|
||
* 支持软件访问和cache访问模式,并发访问时硬件自动仲裁,且flash在program和erase过程中,可以自动挂起,让cache优先访问,提高系统访问的实时性。
|
||
* 支持两个cs访问,cs0大小可配置,访问地址超过cs0时自动切换到cs1,两个cs均最大支持16MB。
|
||
* flash program支持512bytes dual page模式,提高program效率。
|
||
* DP(data path)逻辑实现spi和控制器之间的时钟跨域处理以及数据支持自动加密、解密(AES)。
|
||
* 支持io map逻辑,实现spi接口信号的remapping操作,方便适配不同flash芯片接口。
|
||
* 支持io share,实现sfc和smc io分时复用。
|
||
* 支持spi地址空间remapping。
|
||
* 支持spi接口任意波形发生器(可配置posedge或者negedge收发,可增加dummy cycle来改善io timing)。
|
||
|
||
sfc主要有两种工作模式:
|
||
|
||
* 软件通过sfc寄存器发起的操作,包括program mode,erase mode,data trans mode,register read/write mode。
|
||
* 硬件通过cache下发的read操作。(目前flash不能通过cache write)
|
||
|
||
|
||
|
||
## 测试例程说明
|
||
|
||
sfc测试例程使用到的外设包含sfc、uart、gptimer。
|
||
|
||
* sfc:测试对象,用于测试sfc对外部flash的访问和控制。
|
||
* uart:使用uart0,实现调试信息打印,接收case配置信息等与PC的交互功能。
|
||
* gptimer:使用gptimer0,用于测试“普冉flash工厂模式”时延时功能。
|
||
|
||
sfc测试例程遵循自动化测试框架,在例程执行之后,初始化串口0,通过串口发送“start”字段到PC,并等待PC发送“config”字段来获取本次需要执行的测试case。
|
||
|
||
在获取到case组合之后,初始化flash以及gptimer,并根据case组合开始执行测试case。
|
||
|
||
### case0:获取flash芯片厂商ID和设备ID
|
||
|
||
1. 获取sfdp(the serial flash discoverable parameter)
|
||
|
||
使用data trans mode,发送0x5a指令,读取flash的sfdp。对应的api如下:
|
||
|
||
sfc_sts_type_t sfc_read_sfdp(uint8_t *data, uint32_t raddr, uint32_t size)
|
||
|
||
读取到的sfdp字段含义见《P25Q32L_datasheet_V1.1.pdf》文件,在本例程中,读取了0x00~0x04,0x10~0x14字段,获得的结果如下,其中0x0对应的4个字节数据为固定数据,0x10中的85(hex)则表示厂商ID。(程序中未判断sfdp的值)
|
||
|
||
[INFO] - UNIT_SFC : SFDP data(0x0): 50 44 46 53
|
||
[INFO] - UNIT_SFC : SFDP data(0x10): 3 1 0 85
|
||
|
||
2. 获取厂商ID和设备ID
|
||
|
||
使用register read/write mode,发送RES(read electronic singnature)指令,获取厂商ID,对应的指令以及API情况如下:
|
||
|
||
| 指令 | 说明 | api |
|
||
| ---- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||
| 0x9f | read identification(RDID),<br />返回3字节,分别是manufacturer id(0x85),<br />memory type id(0x60),capacity id(0x16) | int sfc_get_dev_id(uint8_t *data) |
|
||
| 0x90 | read electronic manufacturer id and device id(REMS)<br />返回2字节,分别是manufacturer id(0x85),device id(0x15) | sfc_sts_type_t sfc_qspi_get_id_mult(uint8_t *data, uint8_t mode) |
|
||
| 0x92 | dual i/o read electronic manufacturer id and device id(DREMS)<br />返回6字节,前2字节分别是manufacturer id(0x85),device id(0x15),<br />后4字节是前两个字节的重复 | sfc_sts_type_t sfc_qspi_get_id_mult(uint8_t *data, uint8_t mode) |
|
||
| 0x94 | quad i/o read electronic manufacturer id and device id(QREMS)<br />返回6字节,前2字节分别是manufacturer id(0x85),device id(0x15),<br />后4字节是前两个字节的重复 | sfc_sts_type_t sfc_qspi_get_id_mult(uint8_t *data, uint8_t mode) |
|
||
|
||
已知fpga上memory子板使用的是4m puya flash芯片,在通过api获取到数据之后,会对比数据是否表示puya 4m flash,如果不是,则判断为获取失败,例程返回失败。例程执行的打印如下:
|
||
|
||
[INFO] - UNIT_SFC : start get flash id test, the correct manufacturer id is 0x85, device id is 0x15
|
||
[INFO] - UNIT_SFC : Manufacturer ID(api):0x85, memory type:0x60
|
||
[INFO] - UNIT_SFC : Manufacturer ID(serial):0x85, device id:0x15
|
||
[INFO] - UNIT_SFC : Manufacturer ID(qual):0x85, device id:0x15
|
||
[INFO] - UNIT_SFC : Manufacturer ID(quad):0x85, device id:0x15
|
||
|
||
### case1:读取flash状态寄存器
|
||
|
||
该case用于读取flash状态寄存器,使用register read/write mode,发送0x05(读取状态寄存器bit0~bit7)或者0x35(读取状态寄存器bit8~bit15)来获取flash状态寄存器值。
|
||
|
||
由于在例程执行之初,调用了flash_init()函数,将sfc以及flash配置为quad模式,因此读取到的状态寄存器中,表示quad enable的bit(bit9)应当置位,因此case中会判断该bit,如果未置位,则表示case执行失败。
|
||
|
||
### case2:flash擦除测试
|
||
|
||
该case用于测试flash erase功能,使用erase mode发送erase指令,其流程如下:
|
||
|
||
1. 初始化测试数组,数组全部初始化为0x55.
|
||
2. 将测试数组写入flash。
|
||
3. 使用cache读取写入数据,确保写入成功。
|
||
4. erase flash。
|
||
5. 用cache读取erase之后的数据
|
||
6. 判断写入数据是否为0x55,以及erase之后的数据是否为0xff,如果任意条件不满足,则判定case执行失败。
|
||
|
||
在上述流程中,使用不同的erase指令重复执行步骤2~步骤6,用于验证不同的erase模式功能。erase指令如下:
|
||
|
||
| 指令 | 说明 | api |
|
||
| ---- | --------------------------------- | ------------------------------------------------------------ |
|
||
| 0x81 | 按page(256 bytes)大小擦除flash | int IRAM_ATTR sfc_erase_page(uint32_t page_addr, uint8_t sw_mode) |
|
||
| 0x20 | 按sector(4K bytes)大小擦除flash | int IRAM_ATTR sfc_erase_sector(uint32_t sector_addr, uint8_t sw_mode) |
|
||
| 0xD8 | 按block(64K bytes)大小擦除flash | int IRAM_ATTR sfc_erase_block(uint32_t block_addr, uint8_t sw_mode) |
|
||
| 0xC7 | 擦除整个flash | int sfc_erase_chip(void) |
|
||
|
||
### case3:flash写测试
|
||
|
||
该case用于测试flash写功能,使用program mode,发送写指令和数据,完成写操作,case流程如下:
|
||
|
||
1. 以x55初始化测试数组。
|
||
2. 擦除flash数据。
|
||
3. 写入flash数据。
|
||
4. 使用cache读取写入的数据。
|
||
5. 判断读取的数据是否与写入的数据相同,不同则判定case执行失败。
|
||
|
||
在上述流程中,使用不同的写指令重复步骤2~步骤5,验证不同的写模式,写指令如下:
|
||
|
||
| 指令 | 说明 | api |
|
||
| ---- | ------------------------------------------------- | ------------------------------------------------------------ |
|
||
| 0x02 | page program,以单线模式写入1page flash数据 | int IRAM_ATTR sfc_write(uint8_t *data, uint32_t waddr,uint32_t size,uint8_t prog, uint8_t sw_mode) |
|
||
| 0x32 | quad page program,以4线模式写入1 page flash数据 | int IRAM_ATTR sfc_write(uint8_t *data, uint32_t waddr,uint32_t size,uint8_t prog, uint8_t sw_mode) |
|
||
|
||
### case4:flash读测试
|
||
|
||
该case用于测试flash读功能,使用data trans mode,发送读指令,完成读操作,case流程如下:
|
||
|
||
1. 擦除flash数据
|
||
2. 初始化测试数组,数组第一个字节置为0xbb
|
||
3. 写入测试数组。
|
||
4. 使用sfc api读取写入的数据,仅读取一个字节
|
||
5. 判断读取的数据是否为写入的0xbb,如果不是则判定case执行失败。
|
||
|
||
在上述流程中,使用不同的读指令重复步骤4~步骤5,验证不同的读模式,读指令如下:
|
||
|
||
| 指令 | 说明 | api |
|
||
| ---- | ------------------------------- | ------------------------------------------------------------ |
|
||
| 0x03 | read data bytes | int sfc_read(uint8_t *data, uint32_t raddr, uint32_t size, uint8_t mode) |
|
||
| 0x0B | read data bytes at higher speed | int sfc_read(uint8_t *data, uint32_t raddr, uint32_t size, uint8_t mode) |
|
||
| 0x3B | dual read mode | int sfc_read(uint8_t *data, uint32_t raddr, uint32_t size, uint8_t mode) |
|
||
| 0x6B | quad read mode | int sfc_read(uint8_t *data, uint32_t raddr, uint32_t size, uint8_t mode) |
|
||
| 0xBB | 2*IO read mode | int sfc_read(uint8_t *data, uint32_t raddr, uint32_t size, uint8_t mode) |
|
||
| 0xEB | 4*IO read mode | int sfc_read(uint8_t *data, uint32_t raddr, uint32_t size, uint8_t mode) |
|
||
|
||
### case5:flash全片读写测试
|
||
|
||
该case与case3写测试类似,不过是整片flash的读写测试,其流程如下:
|
||
|
||
1. 以0x55初始化测试数组。
|
||
2. 擦除flash全片
|
||
3. 以quad模式全片写入测试数组数据
|
||
4. 使用cache读取全片,并判断数据是否与写入数据一致,不一致则判定case执行失败。
|
||
5. 将数据初始化为0xaa,重复步骤2~步骤4
|
||
|
||
### case6:普冉flash测试模式(full scan)测试
|
||
|
||
按照puya提供的文档《P25Q32H FT测试方案.pdf》文档,编写测试case代码。puya提供的测试流程如下:
|
||
|
||
◼ Step1:设置QE=1(WREN + WRSR(31H) + 02H) (RDSR(35H) check 02H)
|
||
◼ Step2: WREN + Chip erase(check busy状态判断是否擦完)
|
||
◼ Step3: Enter test mode
|
||
◼ Step4: D4H program 256byte AA,AA,AA,AA,55,55,55,55…AA,AA,AA,AA,55,55,55,55 to 地址0x000000H
|
||
◼ Step5: D4H program 256byte AA,AA,AA,AA,55,55,55,55…AA,AA,AA,AA,55,55,55,55 to 地址0x000100H
|
||
◼ Step6: D4H program 256byte AA,AA,AA,AA,55,55,55,55…AA,AA,AA,AA,55,55,55,55 to 地址0x000200H
|
||
◼ Step7: D4H program 256byte AA,AA,AA,AA,55,55,55,55…AA,AA,AA,AA,55,55,55,55 to 地址0x000300H
|
||
◼ Step8: D4H program 256byte 55,55,55,55, AA,AA,AA,AA…55,55,55,55, AA,AA,AA,AA to 地址0x000400H
|
||
◼ Step9: D4H program 256byte 55,55,55,55, AA,AA,AA,AA…55,55,55,55, AA,AA,AA,AA to 地址0x000500H
|
||
◼ Step10: D4H program 256byte 55,55,55,55, AA,AA,AA,AA…55,55,55,55, AA,AA,AA,AA to 地址0x000600H
|
||
◼ Step11: D4H program 256byte 55,55,55,55, AA,AA,AA,AA…55,55,55,55, AA,AA,AA,AA to 地址0x000700H
|
||
◼ Step8: Exit test mode
|
||
◼ Step9: EBH command verify CKBD
|
||
◼ Step10: WREN + Chip erase
|
||
|
||
编写的测试case中,仅step9(EBH command verify CKBD)未采用puya提供的方法,而是全片使用cache读取数据并判断的方式实现,其他均按照上述流程执行。
|
||
|
||
需要注意的是,在进入测试模式后,flash芯片自动交换了cs和wp信号,需要配置sfc io map,交换cs和wp信号,与flash保持同步。
|
||
|
||
【注】目前在退出测试模式后,将cs和wp信号换回,读取到的flash数据全部为0x00,因此case中在退出测试模式后并未换回cs和wp信号,直接读取flash数据,目前验证数据OK。
|
||
|
||
### case7:多核竞争访问测试
|
||
|
||
该测试为了验证多核间竞争访问flash时,sfc仲裁功能是否有效,读写是否存在异常情况。
|
||
|
||
测试的方法如下:
|
||
|
||
1. 规定每个core独立读写一个page(core0读写page0,core1读写page1,core2读写page2),另外存在一个公用的page3,各core每次写入自己page的同时,将数据一并写入到page3。
|
||
2. core0负责初始化flash的4个page为0,并启动另外2个core。
|
||
3. core1和core2读出各自page的数据,并将得到的值加一,重新写入各自的page和共用的page,重复100次。
|
||
4. core0不写flash,而是使用cache每隔500ms读取一次4个page的数据,重复读取30次,此时core1和core2已经完成100次写入,因此core0最后一次得到的值一定是100,且公用page的值一定等于core1或者core2对应page的值,凭此判断测试是否成功。
|
||
|
||
在开始测试时,确实发现多核访问存在一些问题,比如抓取到的spi波形异常(指令后没有地址、地址信息错误等),或者读取到的数据不符合预期,通过沟通得知,**sfc硬件仲裁机制,是凌驾于sfc之上,服务于cache**。即通过直接控制sfc(即sfc api)访问flash时,需要软件自行仲裁,sfc本身不存在仲裁机制,多核竞争访问时,一旦sfc start标志置位,sfc均以当前的寄存器的值作为配置,开始操作flash,即指令之间能相互打断,此时可能出现指令未执行,或者指令执行出错等情况。
|
||
|
||
针对以上情况,有以下2种解决办法:
|
||
|
||
1. 所有flash的读写擦除,都集中到一个core上操作,当其他core需要操作flash时,通过核间通信将操作告知指定的core。这样的好处是软件可以统一控制操作flash行为,比如等待空闲之后再操作flash等,但是需要与上层拟定方案,并在驱动层做适配。
|
||
2. 使用自旋锁,保证同一时刻仅一个flash操作被执行。好处是对现有代码改动较少,但是core在等待其他core释放自旋锁期间无法做其他事情,相当于浪费了cpu的能力,甚至会减缓某些逻辑的执行。
|
||
|
||
目前采用的是方法2,在sfc驱动层中,使用spinlock0对flash read、write、erase均进行上锁,并在执行完成之后解锁,测试结果OK。
|
||
|
||
***
|
||
|
||
**在后续的讨论中,考虑到spinlock死锁和效率问题,取消了sfc驱动中增加spinlock的办法,因此调整测试方法如下:**
|
||
|
||
1. core0单独读写flash一个page,读取page第一个word数据,擦除后将数据加1重新写入,数据从0开始计数,重复x次。
|
||
2. core1、core2通过cache读取该数据,先使用cache_invalidata函数抛弃无效数据,之后重新读取。判断值是否顺序累加了x次,在达到x次将scratch0寄存器core id * 2对应的bit置位,bit0表示core已经读取到x,bit1表示是否顺序读取到所有值。
|
||
3. core0每次更新数据之后,读取scratch0寄存器,并在core1、core2都读取到x之后不再读写flash,并将core1、core2停止,根据scratch0寄存器的值判定测试是否成功。
|
||
|
||
该测试还兼顾了测试cache优先访问的功能,sfc中存在一个寄存器用于记录flash在erase和program过程中被挂起的次数,在该测试执行前将次数清除,并在执行之后读取该值,能观察到次数显著增加,说明cache确实打断了erase、program过程。
|
||
|
||
### case8:flash地址重映射
|
||
|
||
该测试为了验证sfc所具有的地址重映射功能。
|
||
|
||
**【注】sfc地址重映射功能,仅多cache读生效,即使用sfc api访问flash时,始终使用真实偏移地址。**
|
||
|
||
测试流程如下:
|
||
|
||
1. 将remap单位配置为64KB(默认即为64KB),擦除地址0x00和0x10000。
|
||
2. 将0x00地址写入256字节0x55数据,并用api读取0x00和0x10000地址数据,预期0x00对应的数据为0x55,0x10000对应的数据为0xff。如果数据错误,则判定测试失败。
|
||
3. 通过直接更改寄存器,实现remap 0x00和0x10000地址。
|
||
4. 使用cache重新读取0x00和0x10000数据,预期0x00数据为0xff,0x10000数据为0x55,如果数据错误,则判定失败。
|
||
5. 将重映射还原,并通过cache读取数据,确保还原成功。
|
||
6. 将remap单位配置为128KB,操作地址更改为0x00和0x20000,重复步骤2~步骤4。
|
||
|
||
测试结果OK。
|
||
|
||
### case9:sfc挂载两颗flash芯片
|
||
|
||
该测试为了验证sfc支持双flash的功能,因此需要在测试环境中焊接两颗flash芯片。
|
||
|
||
sfc支持两个cs信号,即可以挂载两个flash芯片,sfc存在SPI_CS0_CFG寄存器,用于配置sfc访问两颗flash的行为,具体行为如下:
|
||
|
||
* 当寄存器中spi_cs1_force_sel置1时,sfc强制访问cs1对应的flash芯片。
|
||
* 当寄存器中spi_cs1_force_sel置0时,通过spi_cs0_size位配置flash 0芯片大小,单位为MB,最大配置为16,如果访问的地址超过flash0配置的空间大小,则自动访问cs1对应的flash。
|
||
|
||
测试流程较为简单,其步骤如下:
|
||
|
||
1. 在flash 0指定位置X写入数据A,并读出数据对比,以确保写入成功,写入失败则判定测试失败。
|
||
2. 将spi_cs1_force_sel置1,在flash 1指定位置X写入数据B,并读取数据确保写入成功,写入失败则判定测试失败。
|
||
3. 读取flash 0指定地址X,判断读出数据是否为数据A,不是则判定失败。
|
||
4. 将spi_cs1_force_sel置0,spi_cs0_size位配置为4。
|
||
5. 读取地址X,判断数据是否为数据A,不是则判定失败。
|
||
6. 读取地址X + 0x400000,判断数据是否为数据B,不是则判定失败。
|
||
|
||
需要注意以下两点:
|
||
|
||
* 如果通过cache读取数据,需要将cache space配置合理,否则超出space的地址将会被抛弃。
|
||
* spi_cs0_size位配置的flash 0大小,需要小于等于flash 0的真实大小。
|
||
|
||
以上测试程序运行时,同步使用逻辑分析仪观察sfc信号,发现cs信号符合预期,测试结果通过。
|
||
|
||
### case10:sfc monitor信号测试
|
||
|
||
该测试为了验证sfc monitor信号是否正常工作。
|
||
|
||
测试流程较为简单,找到可用的gpio,将sfc monitor信号通过io matrix绑定到gpio上,使用逻辑分析已接入sfc真实信号和sfc monitor信号,触发flash操作,观察monitor信号是否与sfc真实信号保持一致。
|
||
|
||
测试结果通过,monitor信号比真实信号晚35ns左右,波形有微小失真,但基本和真实sfc信号保持一致。
|
||
|
||
**【注】fpga上sfc的cs线存在在引出时存在问题,将cs0信号引到了flash1,cs1信号引到了flash0,sfc正常情况下使用的cs0,即真正使用的是flash1,由于memory子板焊接的是flash1,因此fpga保持现状不变。**
|