1、介绍与设计基本流程
Eflash在Soc上的作用:当CPU掉电之后,所有数据会丢失,重新上电后需要重新读取,而Flash作为非易失性的存储器,在掉电之后仍然保有数据,在上电之后CPU可以直接从Eflash中读取数据,重新启动。
想要进行设计,首先需要了解要实现的功能、用到的工具以及信号的定义。如本篇文章的AHB to Eflash设计,需要用到AHB协议和Flash,所以需要熟悉AHB协议的信号;阅读Datasheet表,了解Flash的构成以及信号和功能。
AHB协议可以参考上一篇帖子:AHB to Sram设计,上帖有具体说明。
至于Flash,本设计采用的是两片Flash串联结构,Flash由代工厂提供,提供仿真、时序文件,但是一般不提供GDS,需要在工厂做merge。Flash是32 k*32 bit大小,共有两片Flash串联构成。32 k*32 bit=256 k bytes=250 page。
通过阅读Flash datasheet,理解里面的信号和功能,了解各种信号需要的建立保持时间,了解信号的组成(例如Xaddr信号,高8位为选择page,低2位为选择row),只有理解清楚信号和功能才能对接口和控制器进行设计,具体的信号在后面会详细介绍。
2、信号说明
2.1、Eflash信号说明
通过阅读Flash的datasheet,如下图所示,本设计采用的Flash的信号包括图中的15种。
具体的信号意义如下表所示:
信号 | 意义 | 位宽 |
DIN | 从总线输入的数据 | 32 |
DOUT | 输出到总线的数据 | 32 |
Xadr | 选中eflash的core中某个row,其中高8位选中的是某个page,低2位选中的是该page中的某个row | 10 |
Yadr | 选中某个row中的某32bit地址,一个row等于128 bytes=1024 bits=32*32sbits,也就是选中某32个bit地址。6‘b10_0000表示32,也就是从5’b0_000到5'b1_1111刚好有32个数 | 5 |
XE | Xaddr的使能信号 | 1 |
YE | Yaddr的使能信号 | 1 |
SE | 在读取数据的时候需要拉高SE。在读数据的时候,读取某行某列中存储体单元的数据,存储在晶体管中,驱动力可能不够,需要灵敏放大器,将信号读取到的数据送到输出端。 | 1 |
IFREN | 对Information blck访问的使能信号 | 1 |
ERASE | 页擦除 | 1 |
NVSTR | 非易失性操作,只有当ERASE和PROG的时候需要拉高,也就是当需要改变里面的数据的时候才需要拉高 | 1 |
MAS1 | 块擦除 | 1 |
PROG | 写入数据 | 1 |
VDD | 电源 | \ |
VSS | 地 | \ |
2.2、模式说明
在Flash的模式当中,可以包括四种,如下表所示:包括标准模式、读、写、页擦除、块擦除。这几种模式需要下面7种信号在不同的电平下起作用。
其中页擦除是对Flash中的某一个page也就是某512 bytes数据进行擦除,块擦除是对整块Flash进行擦除,通常需要很久的时间。
模式 | XE | YE | SE | PROG | ERASE | MAS1 | NVSTR |
标准 | L | L | L | L | L | L | L |
读 | H | H | H | L | L | L | L |
写 | H | H | L | H | L | L | H |
页擦除 | H | L | L | L | H | L | H |
块擦除 | H | L | L | L | H | H | H |
2.3、各信号建立保持时间
在本次设计当中,对于写、页擦除、块擦除、读等操作,需要考虑各种信号的建立保持时间。在各种信号达到不同的建立保持时间后才能有效,具体的建立保持时间如下表所示:
如下图所示,NVSTR在ERASE拉高和拉低之后,还需要额外保持5 us,否则可能是无效的。
3、设计思路
常常看了网上的各种代码、教程、设计方案,但是还是卡壳在“为什么要这样设计?”的问题上,所以本篇文章会插入设计思路在里面,后续的文章也会逐渐参入一些设计思路、难点、拓展思考以及解决方案在里面。
3.1、整体框架思路
对于本篇AHB to Eflash的设计,首先需要确定其功能,即通过AHB bus信号控制Flash进行读、写、擦除的操作。考虑到AHB的信号到对Flash的控制信号比较复杂,并且很明显AHB总线信号和Flash接口信号有着较大区别,无法直接将AHB信号引用到Flash接口上,所以需要设计一个面向AHB的接口和面向Flash的控制模块(如果信号简单,并且大部分信号能直接引用,则只设计一个接口模块就行,类似与AHB to Sram)。大致设计如下图所示:
有了大致的设计框架,下面需要细化面向AHB的接口模块和面向Flash的控制模块,因此需要了解两个模块具体实现的功能。
该接口模块是为了将AHB bus的信号转成Flash_control接收的信号,所以需要考虑各个输出信号的来源。
3.2、面向Flash的控制模块设计思路
要设计该控制模块,首先需要清楚输出到Eflash的信号有哪些,需要具备哪些功能。本篇设计中采用的两片Flash Memory Block串联构成一个Eflash。
其中每一块Flash Memory Block为32 k*32 bit的大小(8 bit=1 byte;512 bytes=1 page;1 page=4 row;1 row=128 bytes=1024 bits;)此处YADR是以32bit为一个单位,即一个row=128 bytes=1024 bits=32*32 bits,所有选中某个row中的某32 bits数据,需要32个地址位,换算成二进制也就是从5'b0_0000到5'b1_1111,所以YADR为5位宽的。
Flash Control 设计思路:
考虑到需要这些信号输入,则必须要在Flash Control模块输出这些信号,由于Flash要在不同的状态下响应(包括读、写、页擦除、块擦除),并且读、写、页擦除、块擦除时候需要考虑信号的建立保持时间,于是可以将Flash控制模块写成状态机的形式(例如在在标准状态下,若收到读使能信号,则进入读状态),然后在根据AHB_slave_if接口的输出来确定下一个状态。
其它的信号可以直接在接口模块中完成,然后直接接入到Flash control模块。
至此,Flash Control所需要的功能/模块已经完成。
除了上述设计外,还需要考虑将状态机每次循环一次,做完一个动作之后,给接口模块一个反馈,“告诉接口模块我已经完成了该动作”,所以需要设计一个状态反馈模块。
至此,设计基本上已经完成。如下图所示:
3.3、AHB_flash_if接口模块的设计思路
对于面向AHB的接口模块,该模块需要将AHB总线上的信号转化为Flash控制器“能看懂的信号”。其中输入的地址是来自AHB总线上的32位宽地址,但是要转化为Flash“能看懂”的Xadr和Yadr,那么肯定需要一个地址转化模块/功能,该模块还需要根据地址选中是哪一个Flash。
1、考虑到需要将ahb的总线地址转换成flash的地址,所以肯定需要一个地址转换模块/功能。
2、因为不同的时钟频率下,信号建立保持时间需要的时钟周期是不一样的,所以需要一个建立保持时间的时钟周期计算模块/功能,这里直接通过写寄存器来配置。
3、另外因为Eflash有两片Flash,除此之外还需要考虑是否为boot区,reg区。所以需要实现一个区域的选择模块/功能。
4、除此之外,因为要控制Flash实现读写功能,所以需要生成flash各种信号的模块/功能,其中包括一系列的使能信号,比如XE\YE\SE\nvstr
至此,控制flash所需要的功能和模块基本上已经设计完成。
5、但是,作为一个slave,肯定是需要对AHB和Master做出反馈,那就必须要一个反馈模块/功能。
如下图所示:
至此,接口模块的设计框架基本上已经完成。
4、接口信号设计
在对整体框架基本上搭建完成后,需要具体考虑有哪些接口信号。
4.1、AHB_slave_if接口信号
接口信号分为输入和输出,对于本篇的设计当中的接口模块,输入和输出又可以分为面向Master、面向decoder和向着Flash control的输入输出。
4.1.1、输入信号
(1)来自AHB/Master的信号
来自Master的信号包括h_clk、h_rst_n, hready_in,hwrite, hsize, htrans, hburst, hwdata, haddr,boot_en,addr_offset。elfash_wp_n(写保护信号也是由Master产生?)
(2)来自Flash control的信号
来自Flash control的信号包括flash_rdata, flash_pro_done, flash_busy, hready_flag。
(3)来自decoder的信号
来自decoder的信号只有hsel,当hsel满足的时候,则表示选择了该slave。
具体的信号意义和参数以及位宽如下表所示:
信号名称 | 意义 | 参数 | 位宽 |
H_clk | 时钟 | / | / |
H_rst_n | 复位信号,低电平有效 | / | 1 |
H_trans | 属于AHB控制信号中的一种,用于表示发出的数是否属于同一个burst或者是第一笔数据等,具体可以自行搜索。 | IDLE(2'b00)、BUSY(2'b01)、NONSEQ(2'b10)、SEQ(2'b11) | 2 |
H_size | 表示数据大小 | 8 bit(3'b000)、16 bit(3'b001)、32 bit(3'b010)、64 bit(3'b011) | 3 |
H_burst | 表示传输地址的递增形式,是否需要回弹,,具体的回弹可以参考之前AHB to Sram设计的帖子 | SINGLE(3'b000)、INCR(3'b001)、WRAP4(3'b010)、INCR4(3'b011)、WRAP8(3'b100)、INCR8(3'b101)、WRAP16(3'b110)、INCR8(3'b111) | 3 |
HADDR | 地址 | / | 32 |
H_write | 写使能 | / | 1 |
H_wdata | 写数据 | / | 32 |
eflash_wp_n | 写保护信号 | / | 1 |
boot_en | boot区的使能信号 | / | 1 |
addr_offset | 地址偏移,Boot区域的首地址 | / | 5 |
flash_busy | flash是否busy | / | 1 |
flash_wr_done | 是否写完成 | / | 1 |
flash_pe_done | 页擦除 | / | 1 |
hready_flag | 反馈给AHB总线 | ||
flash_rdata | 读取的数据 | / | 32 |
flash_ctrl_int | CPU来读取的信号,考虑CPU在看到flash_ctrl_int则会来读状态寄存器 | / | |
flash_prog_done | flash烧写是否完成 | / | 1 |
4.1.2、输出信号
(1)输出到AHB/Master信号
输出到AHB/Master的信号基本上都是反馈信号,包括hrdata, hresp (OK \ ERROR \ SPLIT \ RETRY), hready_out。
(2)输出到Flash control模块的信号
输出到Flash control模块的主要包括各种使能信号、时钟复位信号、地址和数据信号、建立保持时间信号、flash选择信号和information/main block的选择信号。
使能信号:flash_prog_en, flash_pe_en, flash_rd_en, SE? nvsrt?
时钟复位信号:flash_clk, flash_rst_n
选择信号:rd_inf0_sel, rd_inf1_sel, rd_main0_sel, rd_main1_sel, flash0_rd_cs, flash1_rd_cs, prog_infrarea0_sel, prog_infrarea1_sel, prog_mainarea0_sel, prog_mainarea1_sel, pe_main_inft_sel
建立保持时间信号:t_nvst_setup, t_nvstr_hold, t_rcv, t_prog_setup, t_prog_hold, t_addr_setup, t_addr_hold, t_prog_proc, t_addr_aces, t_page_erase。具体的建立保持时间可以查看2.3节的第一张图。
地址数据信号:flash_addr_out, flash_wdata, flash_ctrl_int(中断信号), pe_num(页擦除的具体是哪一页)
具体如下表所示:
信号名称 | 意义 | 参数 | 位宽 |
flash_prog_en | flash写使能信号 | / | 1 |
flash_pe_en | 页擦除使能 | / | 1 |
flash_rd_en | 读使能 | / | 1 |
flash_clk | 时钟信号(是否考虑不同时钟) | / | |
flash_rst_n | 复位信号 | / | 1 |
rd_inf0_sel | flash0的information区读选择 | 1/0 | 1 |
rd_inf1_sel | flash1的information区读选择 | 1/0 | 1 |
rd_main0_sel | flash0的main区读选择 | 1/0 | 1 |
rd_main1_sel | flash1的main区读选择 | 1/0 | 1 |
flash0_rd_cs | 是否选择flash0 | 1/0 | 1 |
flash1_rd_cs | 是否选择flash1 | 1/0 | 1 |
prog_infrarea0_sel | 写flash0的information区域选择 | 1/0 | 1 |
prog_infrarea1_sel | 写flash1的information区域选择 | 1/0 | 1 |
prog_mainarea0_sel | 写flash0的main区域选择 | 1/0 | 1 |
prog_mainarea1_sel | 写flash1的main区域选择 | 1/0 | 1 |
pe_main_infr_sel | 擦除main还是擦除information的页 | 1/0 | 1 |
t_nvstr_setup | nvstr信号的建立时间>5 us | / | 12 |
t_nvstr_hold | nvstr信号的保持时间>5us | / | 12 |
t_rcv | 恢复时间>1us | / | 8 |
t_prog_setup | 写信号后nvstr的建立时间 | / | 16 |
t_prog_hold | 写的保持时间(20~40 us) | / | 4 |
t_addr_setup | 地址/数据的建立时间 | / | 4 |
t_addr_hold | 地址/数据的保持时间 | / | 4 |
t_prog_proc | 写时间 | / | 16 |
t_addr_aces | 读时间 | / | 8 |
t_page_erase | 页擦除时间>20 ms | / | 24 |
flash_addr_out | 地址 | / | 32 |
flash_wdata | 数据 | / | 32 |
flash_ctrl_int | 中断信号 | / | 1 |
pe_num | 页擦除,哪一页 | / | 9 |
4.2、Flash Control模块信号介绍
信号分为输入和输出,其中输入和输出都可以分“来自AHB_slave_if的信号”和“来自Flash的信号”。
4.2.1、输入信号
(1)来自AHB_slave_if接口模块的输入信号
来自AHB_slave_if接口模块的输入信号主要包括时钟复位信号、各种使能信号、选择信号、数据地址信号、建立保持时间信号
时钟复位信号:flash_clk, flash_rst_n
使能信号:prog_en, pe_en, read_en, flash_ctrl_int
选择信号:rd_inf0_sel, rd_inf1_sel, rd_main0_sel, rd_main1_sel, flash0_rd_cs, flash1_rd_cs, prog_infrarea0_sel, prog_infrarea1_sel, prog_mainarea0_sel, prog_mainarea1_sel, pe_main_infr_sel,
数据地址信号:pe_num, flash_addr, flash_data_in
建立保持时间信号:nvsrt_set_timing, nvstr_hold_timing, rcv_timing , prog_set_timing, prog_hold_timing, addr_set_timing, addr_set_timing, prog_proc_timing, addr_aces_timing, page_erase_timing,
(2)来自Flash的输入信号
来自Flash的输入信号只有读数据的时候会有,也就是flash输出的读取数据,分为flash0和flash1两个不同的flash读出的数据: flash0_rdata, flash1_rdata
4.2.2、输出信号
输出的信号主要包括输出到AHB_slave_if接口模块的输出信号和输出到不同的flash的输出信号。
(1)输出到接口模块的信号
输出到接口模块的信号只有输出到接口模块的数据:flash_rd_data
(2)输出到flash的信号
输出到flash的信号分成两片(flash0和flash1),即flash所需要的信号,具体可以参考2.1节的第一张图。
此处考虑到Flash control模块的输入输出信号和上述的信号基本相似,或者在上述内容在已经提过,因此不在制作表格进行赘述。
5、代码实现难点
至此,设计框架已经完成,下面会进行代码的实现,但是在代码的实现过程中常常存在一些难点,在本节进行说明和分析。
想要对该接口模块进行代码实现,就要先明确功能和操作。在该设计中,实现的是Master对flash读、写、页擦除、块擦除和对slave的状态、配置读写,因此master对slave进行操作的时候,首先需要明确对slave的哪个地方进行操作,然后是进行怎么样的操作。
只有收到hsel信号,即选中我这个slave信号之后,我才把ahb上面的控制信号接入到内部寄存器中,否则直接使用ahb信号会导致无论是否选中,ahb的信号都有效了。
5.1、AHB_slave_if接口模块代码实现难点
5.1.1、地址的转换?
地址的生成包括了读、写reg区的地址,读、写、擦flash的地址。
(1)reg区的读、写地址产生:
读、写寄存器的地址都是通过ahb总线地址来的,当满足读reg区域的使能信号时,则根据ahb总线给出的地址低8位来判断是读、写哪个寄存器:
以写为例,代码如下:读地址也是一样为h_addr_r[7:0]
always@(posedge hclk or negedge hresetn)
begin
if (!hresetn)
begin
nvstr_setup_timing <= 32'h259 ;
nvstr_hold_timing <= 32'h259 ;
rcv_timing <= 32'h79 ;
prog_setup_timing <= 32'h4b1 ;
progaddr_sethold_timing <= 32'h333 ;
prog_proc_timing <= 2'h962 ;
wr_en_r <= 1'b0 ;
pe_en_r <= 1'b0 ;
pe_num_r <= 9'h1df ;
pe_main_infr_sel_r <= 1'b0 ;
prog_addr_r <= 32'h0 ;
prog_data_r <= 32'h0 ;
int_en_r <= 32'h0 ;
invalid_data_r <= 32'h0 ;
end
else if (ahb_wr_en && reg_sel)
begin
case(haddr_r[7:0])
NVSTR_SETUP_ADDR : nvstr_setup_timing <= hwdata;
NVSTR_HOLD_ADDR : nvstr_hold_timing <= hwdata;
RCV_ADDR : rcv_timing <= hwdata;
PROG_SETUP_ADDR : prog_setup_timing <= hwdata;
PROGADDR_SETHOLD_ADDR : progaddr_sethold_timing <= hwdata;
PROG_PROC_ADDR : prog_proc_timing <= hwdata;
RD_ACES_ADDR : rd_aces_timing <= hwdata;
PE_ADDR : pe_timing <= hwdata;
WR_EN_ADDR : wr_en_r <= hwdata[0]; //software set
PE_CONFIG_ADDR : pe_en_r <= hwdata[0];
PE_NUM_ADDR : pe_num_r <= hwdata[8:0];
PE_MIANINFR_SEL_ADDR : pe_main_infr_sel_r <= hwdata[0];
PROG_ADDR_ADDR : prog_addr_r <= hwdata;
PROG_DATA_ADDR : prog_data_r <= hwdata;
PROG_DATA1_ADDR : prog_data1_r <= hwdata;
PROG_DATA2_ADDR : prog_data2_r <= hwdata;
PROG_DATA3_ADDR : prog_data3_r <= hwdata;
INT_EN_ADDR : int_en_r <= hwdata;
default : invalid_data_r <= hwdata;
endcase
end
else if (flash_prog_done || boot_wr_done)
begin
wr_en_r <= 1'b0;
prog_addr_r <= 32'h3bfff;
end
else if (flash_pe_done || boot_pe_done)
begin
pe_en_r <= 1'b0;
pe_num_r <= 9'h1df;
end
end
(2)flash读地址的产生
flash的读地址产生要根据是否读boot区域进行改变,当读boot区时,需要加上地址偏移,如果只是读flash,那么直接将ahb给出的地址赋值给读地址,然后去掉后两位即可(这是因为ahb给出的地址为32位的,并且以一个byte为一个地址,而flash中共需要15位宽,并且一个地址位位32 bits=4 bytes)。
assign flash_addr = (flash_prog_en) ? prog_addr_r :
(boot_en && rd_main_sel)?{haddr[31:18],haddr[17:13]|addr_offset,haddr[12:0]}:haddr;
assign flash_addr_out = flash_addr[16:2];
(3)flash写和擦地址的产生
flash的写和擦地址都是来自于寄存器配置进来的:
具体可以参考5.1.1中的第(1)代码里面的其中两行:
PROG_ADDR_ADDR : prog_addr_r <= hwdata;
和
PE_NUM_ADDR : pe_num_r <= hwdata[8:0];
5.1.2、使能信号的产生?
使能信号分为读使使能、写使能、擦使能,根据操作的区域分为读reg、写reg;读flash、写flash、擦flash;其中读、写、擦flash又可以分成boot区、information block、main block来讨论。
(1) reg读使能产生:
reg读使能通过ahb控制信号来产生,当ahb控制信号满足要读的时候,并且选中的地址也是在reg区的时候,则会根据具体给出的地址来读相应的寄存器。即:
assign ahb_rd_en = ((h_trans_r == NONSEQ) || (h_trans_r == SEQ)) && (!h_write_r);
assign reg_sel = (h_addr_r[23:12] == REG_ADDR);
其中h_tranr_r和h_write_r为打拍后的信号,需要满足h_sel和h_ready_in。如下所示:
always@(posedge hclk or negedge hresetn) begin
if(!hresetn)
begin
hsize_r <= 1'b0 ;
htrans_r <= 3'b0 ;
hburst_r <= 3'b0 ;
hwdata_r <= 2'b0 ;
haddr_r <= 32'b0;
end
else if (hsel && hready_in)
begin
hsize_r <= hsize ;
htrans_r <= htrans ;
hburst_r <= hburst ;
hwdata_r <= hwdata ;
haddr_r <= haddr ;
end
else
begin
hsize_r <= 1'b0 ;
htrans_r <= 3'b0 ;
hburst_r <= 3'b0 ;
hwdata_r <= 2'b0 ;
haddr_r <= 32'b0;
end
end
当ahb_rd_en和reg_sel都满足的时候,即为读reg寄存器,根据具体的地址读对应的寄存器。
(2)reg写使能的产生
写reg区的写使能信号也是通过ahb控制信号产生的,与reg读使能类似,当ahb控制信号满足写时,且地址选中reg区时候,即为reg区的写使能:
assign ahb_rd_en = ((h_trans_r == NONSEQ) || (h_trans_r == SEQ)) && (h_write_r);
assign reg_sel = (h_addr_r[23:12] == REG_ADDR);
至于h_trans_r和h_write_r可以参考上面的reg读使能。
(3)flash的读使能
与reg的读使能不同,flash的读使能不能使用打拍过后的ahb控制信号,因为读flash本来就需要等上较长的时间,在等待的时候slave会占用ahb总线,降低效率,所以直接使用ahb控制信号。flash的读使能除了ahb控制信号满足条件外,还需要地址选中flash并且保证flash不是busy状态。
ahb控制信号产生flash读使能:
assign f_rd_en = h_sel && ((h_trans == NONSEQ) || (h_trans == SEQ)) && (!h_write);
assign flash_rd_en = f_rd_en && (!flash_busy) && (flash0_cs || flash1_cs);
其中flash0_cs和flash1_cs产生是根据地址位来判断是否为information block或者main block,具体代码如下:
assign rd_infr_sel = (haddr[23:12] == INFR_ADDR);
assign rd_infr0_sel = rd_infr_sel && (haddr[10] == INFR0_ADDR);
assign rd_infr1_sel = rd_infr_sel && (haddr[11:10] == INFR1_ADDR);
assign rd_main_sel = (haddr[23:18] == MAIN_ADDR);
assign rd_main0_sel = rd_mian_sel && (flash_addr[17] == MAIN0_ADDR);
assign rd_main1_sel = rd_mian_sel && (flash_addr[17] == MAIN1_ADDR);
assign flash0_rd_cs = (rd_infr0_sel || rd_main0_sel);
assign flash1_rd_cs = (rd_infr1_sel || rd_main1_sel);
(4)非boot区的flash写使能
写使能需要分成是非boot区还是写flash非boot区,与读flash不同的是写boot更严格,其使能和地址来自于配置寄存器,但还需要Master给出的boot_en和eflash_wr_n使能信号。需要注意的是,此处把对boot区的写和非boot区的写设置在同一个寄存器中存储,当有flash写使能的时候,可能为boot区写和非boot区的写,需要具体判断。
当对非boot区进行写,需要是非boot区地址(在后面的第(5)小节提及),且写保护为高电平,并且通过寄存器配进来的为写使能。如果地址没问题,且寄存器配置的为写,也不是写保护,则会拉高flash_en写使能(此时为对非boot区的写使能)代码如下:
assign non_boot_addr_correct = !(boot_wr_sel || boot_pe_sel);//是否为boot区
assign flash_addr_correct = boot_protect_n ? 1'b1 : non_boot_addr_correct;//判断地址是否正确
assign wr_en = wr_en_r && flash_addr_correct;
assign flash_prog_en = eflash_wp_n && (wr_en == 1'b1);
(5)boot区的写使能
当对boot区进行写或者擦的时候即为选中了boot区(需要通过给出的偏移地址和写地址来判断),代码如下:
assign boot_protect_n = boot_en;
assign boot_wr_sel = (addr_offset_r == 5'b11111) ? (prog_addr_r[17:13] == 5'b11111 ):
(addr_offset_r == 5'b11110) ? (prog_addr_r[17:14] == 4'b1111 ): 1'b0;
assign boot_pe_sel = (addr_offset_r == 5'b11111) ? (pe_num_r[8:4] == 5'b11111 ):
(addr_offset_r == 5'b11110) ? (pe_num_r[8:5] == 4'b1111 ): 1'b0;
当选中了boot区还需要Master给出的boot_en使能信号,才能对boot区域进行更改。如果没有给出boot使能,而写的地址又是需要更改boot区内容,则会进行报错反馈:
assign non_boot_addr_correct = !(boot_wr_sel || boot_pe_sel);
assign flash_addr_correct = boot_protect_n ? 1'b1 : non_boot_addr_correct;
当给出的boot区写地址是正确的时候,并且通过寄存器配进来的信号为写的时候,且Master给出的写保护为高电平时(低电平有效),则会给出flash的写使能(对boot区的)。
assign wr_en = wr_en_r && flash_addr_correct;
assign flash_prog_en = eflash_wp_n && (wr_en == 1'b1);
(6)页擦除使能
页擦除使能和flash的写使能一样,需要分成boot和非boot区进行讨论,但是最后代码实现写在了一起。此处可参考上述的5.1.2中的(4)和(5)小节,直接给出对应代码,不再赘述:
assign pe_en = pe_en_r && flash_addr_correct;
assign flash_pe_en = eflash_wp_n && (pe_en == 1'b1);
assign pe_num = pe_num_r;
其中flash_addr_correct的来源可以参考5.1.2中的(4)和(5)节。
5.2、Flash control控制模块代码实现难点
flash control最大的难点就是状态机的实现。
5.2.1、状态机
flash control的状态机以三段式状态机的形式写的,三段状态机的写法后续会出一章进行说明,这里不再赘述基本内容,不太明白的小伙伴可以自己搜索三段式状态机的写法。
(1)三段式状态机第一个进程
状态机的第个进程为时序逻辑,主要给当前状态赋值,当复位信号有效的时候则赋0,无效时,每一个时钟上升沿则会赋下一个状态给当前状态。
always@ (posedge flash_clk or negedge flash_rst_n ) begin
if (!flash_rst_n)
begin
flash_current_st <= FLASH_IDLE;
addr_acces_cnt <= 0;
flash0_xaddr_r <= 0;
flash0_yaddr_r <= 0;
flash1_xaddr_r <= 0;
flash1_yaddr_r <= 0;
flash_wdata_r <= 0;
flash0_xe_r <= 0;
flash0_ye_r <= 0;
flash0_se_r <= 0;
flash0_infren_r <= 0;
flash0_erase_r <= 0;
flash0_mass_r <= 0;
flash0_prog_r <= 0;
flash0_nvstr_r <= 0;
flash1_xe_r <= 0;
flash1_ye_r <= 0;
flash1_se_r <= 0;
flash1_infren_r <= 0;
flash1_erase_r <= 0;
flash1_mass_r <= 0;
flash1_prog_r <= 0;
flash1_nvstr_r <= 0;
flash_busy_r <= 0;
flash0_cs_r <= 0;
flash1_cs_r <= 0;
end
else begin
flash_current_st <= flash_next_st;
addr_acces_cnt <= addr_acces_cnt_next;
flash0_xaddr_r <= flash0_xaddr_r_next ;
flash0_yaddr_r <= flash0_yaddr_r_next ;
flash1_xaddr_r <= flash1_xaddr_r_next ;
flash1_yaddr_r <= flash1_yaddr_r_next ;
flash_wdata_r <= flash_wdata_r_next ;
flash0_xe_r <= flash0_xe_r_nexy;
flash0_ye_r <= flash0_ye_r_next;
flash0_se_r <= flash0_se_r_next;
flash0_infren_r <= flash0_infren_r_next;
flash0_erase_r <= flash0_erase_r_next;
flash0_mass_r <= flash0_mass_r_next;
flash0_prog_r <= flash0_prog_r_next;
flash0_nvstr_r <= flash0_nvstr_r_next;
flash1_xe_r <= flash1_xe_r_nexy;
flash1_ye_r <= flash1_ye_r_next;
flash1_se_r <= flash1_se_r_next;
flash1_infren_r <= flash1_infren_r_next;
flash1_erase_r <= flash1_erase_r_next;
flash1_mass_r <= flash1_mass_r_next;
flash1_prog_r <= flash1_prog_r_next;
flash1_nvstr_r <= flash1_nvstr_r_next;
flash_busy_r <= flash_busy_r_next;
flash0_cs_r <= flash0_cs_r_next;
flash1_cs_r <= flash1_cs_r_next;
end
end
(2)三段式状态机第个进程
第二个进程为组合逻辑,直接根据当前状态和当前的输入来判断下一个状态。
always@(*) begin
flash_next_st = flash_current_st;
case (flash_current_st)
FLASH_IDLE:
begin
if (pe_en || prog_en)
flash_next_st = FLASH_TNVS;
else if (read_en)
flash_current_st = READ_ACCESS;
else
flash_next_st = FLASH_IDLE;
end
READ_ACCESS:
begin
if (!rd_finish_pre)
flash_next_st = READ_ACCESS;
else
flash_next_st = FLASH_IDLE;
end
FLASH_TNVS:
begin
if(tnvs_finish)
begin
if(pe_en)
flash_next_st = PE_ERASE;
else if (prog_en)
flash_next_st = PROG_SETUP;
else
flash_next_st = IDLE;
end
else
flash_next_st = FLASH_TNVS;
end
PE_ERASE:
begin
if(pe_finish)
flash_next_st = FLASH_TVNH;
else
flash_next_st = PE_ERASE;
end
FLASH_TVNH:
begin
if(tnvh_finish)
flash_next_st = RCV_TIME;
else
flash_next_st = FLASH_TVNH;
end
RCV_TIME:
begin
if(rcv_finish)
flash_next_st = FLASH_IDLE;
else
flash_next_st = RCV_TIME;
end
PROG_SETUP:
begin
if(prog_set_finish)
flash_next_st = ADDR_SETUP;
else
flash_next_st = PROG_SETUP;
end
ADDR_SETUP:
begin
if (prog_en)
begin
if(prog_addr_hold_finish)
flash_next_st = PROG_PROC;
else
flash_next_st = ADDR_SETUP;
end
else
flash_next_st = FLASH_IDLE;
end
PROG_PROC:
begin
if(prog_en)
begin
if (prog_proc_finish)
flash_next_st = ADDR_HOLD;
else
flash_next_st = PROG_PROC;
end
else
flash_next_st = FLASH_IDLE;
end
ADDR_HOLD:
begin
if(prog_en)
begin
if(prog_addr_hold_finish)
flash_next_st = PROG_HOLD;
else
flash_next_st = ADDR_HOLD;
end
else
flash_next_st = FLASH_IDLE;
end
PROG_HOLD:
begin:
if(prog_en)
begin
if(prog_hold_finish)
flash_next_st = FLASH_TVNH;
else
flash_next_st = PROG_HOLD;
end
else
flash_next_st = FLASH_IDLE;
end
default : flash_next_st <= FLASH_IDLE;
endcase
end
(3)三段式状态机第三个进程
第三个进程为,主要是“告诉”状态机在当前状态下应该“做什么事情”。
always@ (*) begin
hready_flag = 1'b1;
addr_acces_cnt_next = 0;
flash0_xaddr_r_next = flash0_xaddr_r ;
flash0_yaddr_r_next = flash0_yaddr_r ;
flash1_xaddr_r_next = flash1_xaddr_r ;
flash1_yaddr_r_next = flash1_yaddr_r ;
flash_wdata_r_next = flash_wdata_r ;
flash0_xe_r_next = flash0_xe_r ;
flash0_ye_r_next = flash0_ye_r ;
flash0_se_r_next = flash0_se_r ;
flash0_infren_r_next= flash0_infren_r ;
flash0_erase_r_next = flash0_erase_r ;
flash0_mass_r_next = flash0_mass_r ;
flash0_prog_r_next = flash0_prog_r ;
flash0_nvstr_r_next = flash0_nvstr_r ;
flash1_xe_r_next = flash1_xe_r ;
flash1_ye_r_next = flash1_ye_r ;
flash1_se_r_next = flash1_se_r ;
flash1_infren_r_next= flash1_infren_r ;
flash1_erase_r_next = flash1_erase_r ;
flash1_mass_r_next = flash1_mass_r ;
flash1_prog_r_next = flash1_prog_r ;
flash1_nvstr_r_next = flash1_nvstr_r ;
flash_busy_r_next = flash_busy_r ;
flash0_cs_r_next = flash0_cs_r ;
flash1_cs_r_next = flash1_cs_r ;
case(flash_current_st)
FLASH_IDLE:
begin
hready_flag = 1'b1;
case(flash_next_st)
READ_ACCESS:
begin
flash0_xaddr_r_next = (rd_infr0_sel) ?{7'b0,flash_addr[XADDR-1:XADDR_LOW-1]} :
flash_addr[XADDR+XADDR_LOW-2:XADDR_LOW-1];
flash1_xaddr_r_next = (rd_infr1_sel) ?{7'b0,flash_addr[XADDR-1:XADDR_LOW-1]} :
flash_addr[XADDR+XADDR_LOW-2:XADDR_LOW-1];
flash0_yaddr_r_next = flash_addr[YADDR-1:0];
flash1_yaddr_r_next = flash_addr[YADDR-1:0];
flash0_xe_r_next = (rd_main0_sel || rd_infr0_sel);
flash0_ye_r_next = (rd_main0_sel || rd_infr0_sel);
flash0_se_r_next = (rd_main0_sel || rd_infr0_sel);
flash0_infren_r_next= rd_infr0_sel ;
flash1_xe_r_next = (rd_main1_sel || rd_infr1_sel);
flash1_ye_r_next = (rd_main1_sel || rd_infr1_sel);
flash1_se_r_next = (rd_main1_sel || rd_infr1_sel);
flash1_infren_r_next= rd_infr1_sel ;
flash_busy_r_next = 1'b1;
flash0_cs_r_next = flash0_rd_cs;
flash1_cs_r_next = flash1_rd_cs;
end
FLASH_TNVS
begin
flash_busy_r_next = 1'b1;
if(prog_en)
begin
flash0_xe_r_next = (prog_infrarea0_sel || prog_mainarea0_sel);
flash0_infren_r_next= prog_infrarea0_sel;
flash0_prog_r_next = (prog_infrarea0_sel || prog_mainarea0_sel);
flash1_xe_r_next = (prog_infrarea1_sel || prog_mainarea1_sel);
flash1_infren_r_next= prog_infrarea1_sel;
flash1_prog_r_next = (prog_infrarea1_sel || prog_mainarea1_sel);
flash0_xaddr_r_next = (prog_infrarea0_sel) ?{7'b0,flash_addr[XADDR_LOW+1 :XADDR_LOW-1]} :
flash_addr[XADDR+XADDR_LOW-2:XADDR_LOW-1];
flash0_xaddr_r_next = (prog_infrarea0_sel) ?{7'b0,flash_addr[XADDR_LOW+1 :XADDR_LOW-1]} :
flash_addr[XADDR+XADDR_LOW-2:XADDR_LOW-1];
end
else if(pe_en)
begin
if(pe_main_infr_sel)//info select
begin
if(pe_num[1] == 1'b0) //the first flash
begin
flash0_xe_r_next = 1'b1;
flash0_ye_r_next = 1'b1;
flash0_se_r_next = 1'b1;
flash0_infren_r_next= {7'b0,pe_num[0],2'b0};
end
else
begin
flash1_xe_r_next = 1'b1;
flash1_ye_r_next = 1'b1;
flash1_se_r_next = 1'b1;
flash1_infren_r_next= {7'b0,pe_num[0],2'b0};
end
end
else //main select
begin
if(pe_num[8] == 1'b0) //the first flash
begin
flash0_xe_r_next = 1'b1;
flash0_ye_r_next = 1'b1;
flash0_se_r_next = 1'b1;
flash0_infren_r_next= {pe_num[7:0],2'b0};
end
else
begin
flash1_xe_r_next = 1'b1;
flash1_ye_r_next = 1'b1;
flash1_se_r_next = 1'b1;
flash1_infren_r_next= {{pe_num[7:0],2'b0};
end
end
end
end
default :
begin
addr_acces_cnt_next = 0;
flash0_xaddr_r_next = 0;
flash0_yaddr_r_next = 0;
flash1_xaddr_r_next = 0;
flash1_yaddr_r_next = 0;
flash_wdata_r_next = 0;
flash0_xe_r_next = 0;
flash0_ye_r_next = 0;
flash0_se_r_next = 0;
flash0_infren_r_next= 0;
flash0_erase_r_next = 0;
flash0_mass_r_next = 0;
flash0_prog_r_next = 0;
flash0_nvstr_r_next = 0;
flash1_xe_r_next = 0;
flash1_ye_r_next = 0;
flash1_se_r_next = 0;
flash1_infren_r_next= 0;
flash1_erase_r_next = 0;
flash1_mass_r_next = 0;
flash1_prog_r_next = 0;
flash1_nvstr_r_next = 0;
flash_busy_r_next = 0;
flash0_cs_r_next = 0;
flash1_cs_r_next = 0;
end
endcase
end
READ_ACCESS:
begin
hread = 1'b0;
addr_acces_cnt_next = addr_acces_cnt + 1;
if (flash_next_st == FLASH_IDLE)
begin
flash_busy_r_next = 1'b0;
flash0_cs_r_next = 1'b0;
flash1_cs_r_next = 1'b0;
flash0_xaddr_r_next = 0;
flash0_yaddr_r_next = 0;
flash1_xaddr_r_next = 0;
flash1_yaddr_r_next = 0;
flash0_xe_r_next = 0;
flash0_ye_r_next = 0;
flash0_se_r_next = 0;
flash0_infren_r_next= 0;
flash1_xe_r_next = 0;
flash1_ye_r_next = 0;
flash1_se_r_next = 0;
flash1_infren_r_next= 0;
end
else
flash_busy_r_next = 1'b01;
end
FLASH_TNVS:
begin
if(flash_next_st == PROG_SETUP)
begin
flash0_nvstr_r_next = (prog_infrarea0_sel || prog_mainarea0_sel);
flash1_nvstr_r_next = (prog_infrarea1_sel || prog_mainarea1_sel);
end
else if (flash_next_st == PE_ERASE)
begin
if(pe_main_infr_sel) //info select
begin
if(pe_num[1] == 1'b0) //the first flsh
flash0_nvstr_r_next = 1'b1;
else
flash1_nvstr_r_next = 1'b1;
end
else //main select
begin
if(pe_num[8] == 1'b0 ) //the first flsh
flash0_nvstr_r_next = 1'b1;
else
flash1_nvstr_r_next = 1'b1;
end
end
end
PE_ERASE:
begin
if(flash_next_st == FLASH_TVNH)
begin
flash0_erase_r_next = 1'b0;
flash1_erase_r_next = 1'b0;
end
end
PROG_SETUP:
begin
if(flash_next_st == ADDR_SETUP)
begin
flash0_yaddr_r_next = flash_yaddr[YADDR-1:0];
flash1_yaddr_r_next = flash_yaddr[YADDR-1:0];
flash_wdata_r_next = flash_data_in;
end
end
ADDR_SETUP:
begin
if(flash_next_st == PROG_PROC)
begin
flash0_ye_r_next = (prog_infrarea0_sel || prog_mainarea0_sel);
flash1_ye_r_next = (prog_infrarea1_sel || prog_mainarea1_sel);
end
end
PROG_PROC:
begin
if(flash_next_st == ADDR_HOLD)
begin
flash0_ye_r_next = 1'b0;
flash1_ye_r_next = 1'b0;
end
end
ADDR_HOLD:
begin
if(flash_next_st == PROG_HOLD)
begin
flash0_yaddr_r_next = 1'b0;
flash1_yaddr_r_next = 1'b0;
end
end
PROG_HOLD:
begin
if(flash_next_st == FLASH_TVNH)
begin
flash0_prog_r_next = 1'b0;
flash1_prog_r_next = 1'b0;
end
end
FLASH_TVNH:
begin
if(flash_next_st == RCV_TIME)
begin
flash0_xe_r_next = 1'b0;
flash1_xe_r_next = 1'b0;
flash0_nvstr_r_next = 1'b0;
flash1_nvstr_r_next = 1'b0;
end
end
RCV_TIME:
begin
if(flash_next_st == FLASH_IDLE)
flash_busy_r_next = 1'b0;
else
flash_busy_r_next = 1'b1;
end
default : hready_flag = 1'1b;
endcase
end
end