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
-
获取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,0x100x14字段,获得的结果如下,其中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
-
获取厂商ID和设备ID
使用register read/write mode,发送RES(read electronic singnature)指令,获取厂商ID,对应的指令以及API情况如下:
指令 说明 api 0x9f read identification(RDID),
返回3字节,分别是manufacturer id(0x85),
memory type id(0x60),capacity id(0x16)int sfc_get_dev_id(uint8_t *data) 0x90 read electronic manufacturer id and device id(REMS)
返回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)
返回6字节,前2字节分别是manufacturer id(0x85),device id(0x15),
后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)
返回6字节,前2字节分别是manufacturer id(0x85),device id(0x15),
后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(读取状态寄存器bit0bit7)或者0x35(读取状态寄存器bit8bit15)来获取flash状态寄存器值。
由于在例程执行之初,调用了flash_init()函数,将sfc以及flash配置为quad模式,因此读取到的状态寄存器中,表示quad enable的bit(bit9)应当置位,因此case中会判断该bit,如果未置位,则表示case执行失败。
case2:flash擦除测试
该case用于测试flash erase功能,使用erase mode发送erase指令,其流程如下:
- 初始化测试数组,数组全部初始化为0x55.
- 将测试数组写入flash。
- 使用cache读取写入数据,确保写入成功。
- erase flash。
- 用cache读取erase之后的数据
- 判断写入数据是否为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流程如下:
- 以x55初始化测试数组。
- 擦除flash数据。
- 写入flash数据。
- 使用cache读取写入的数据。
- 判断读取的数据是否与写入的数据相同,不同则判定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流程如下:
- 擦除flash数据
- 初始化测试数组,数组第一个字节置为0xbb
- 写入测试数组。
- 使用sfc api读取写入的数据,仅读取一个字节
- 判断读取的数据是否为写入的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的读写测试,其流程如下:
- 以0x55初始化测试数组。
- 擦除flash全片
- 以quad模式全片写入测试数组数据
- 使用cache读取全片,并判断数据是否与写入数据一致,不一致则判定case执行失败。
- 将数据初始化为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仲裁功能是否有效,读写是否存在异常情况。
测试的方法如下:
- 规定每个core独立读写一个page(core0读写page0,core1读写page1,core2读写page2),另外存在一个公用的page3,各core每次写入自己page的同时,将数据一并写入到page3。
- core0负责初始化flash的4个page为0,并启动另外2个core。
- core1和core2读出各自page的数据,并将得到的值加一,重新写入各自的page和共用的page,重复100次。
- 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种解决办法:
- 所有flash的读写擦除,都集中到一个core上操作,当其他core需要操作flash时,通过核间通信将操作告知指定的core。这样的好处是软件可以统一控制操作flash行为,比如等待空闲之后再操作flash等,但是需要与上层拟定方案,并在驱动层做适配。
- 使用自旋锁,保证同一时刻仅一个flash操作被执行。好处是对现有代码改动较少,但是core在等待其他core释放自旋锁期间无法做其他事情,相当于浪费了cpu的能力,甚至会减缓某些逻辑的执行。
目前采用的是方法2,在sfc驱动层中,使用spinlock0对flash read、write、erase均进行上锁,并在执行完成之后解锁,测试结果OK。
在后续的讨论中,考虑到spinlock死锁和效率问题,取消了sfc驱动中增加spinlock的办法,因此调整测试方法如下:
- core0单独读写flash一个page,读取page第一个word数据,擦除后将数据加1重新写入,数据从0开始计数,重复x次。
- core1、core2通过cache读取该数据,先使用cache_invalidata函数抛弃无效数据,之后重新读取。判断值是否顺序累加了x次,在达到x次将scratch0寄存器core id * 2对应的bit置位,bit0表示core已经读取到x,bit1表示是否顺序读取到所有值。
- 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时,始终使用真实偏移地址。
测试流程如下:
- 将remap单位配置为64KB(默认即为64KB),擦除地址0x00和0x10000。
- 将0x00地址写入256字节0x55数据,并用api读取0x00和0x10000地址数据,预期0x00对应的数据为0x55,0x10000对应的数据为0xff。如果数据错误,则判定测试失败。
- 通过直接更改寄存器,实现remap 0x00和0x10000地址。
- 使用cache重新读取0x00和0x10000数据,预期0x00数据为0xff,0x10000数据为0x55,如果数据错误,则判定失败。
- 将重映射还原,并通过cache读取数据,确保还原成功。
- 将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。
测试流程较为简单,其步骤如下:
- 在flash 0指定位置X写入数据A,并读出数据对比,以确保写入成功,写入失败则判定测试失败。
- 将spi_cs1_force_sel置1,在flash 1指定位置X写入数据B,并读取数据确保写入成功,写入失败则判定测试失败。
- 读取flash 0指定地址X,判断读出数据是否为数据A,不是则判定失败。
- 将spi_cs1_force_sel置0,spi_cs0_size位配置为4。
- 读取地址X,判断数据是否为数据A,不是则判定失败。
- 读取地址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保持现状不变。