AHB to Eflash的控制器设计

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中的某个row10
Yadr选中某个row中的某32bit地址,一个row等于128 bytes=1024 bits=32*32sbits,也就是选中某32个bit地址。6‘b10_0000表示32,也就是从5’b0_000到5'b1_1111刚好有32个数5
XEXaddr的使能信号1
YEYaddr的使能信号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进行擦除,通常需要很久的时间。

模式XEYESEPROGERASEMAS1NVSTR
标准LLLLLLL
HHHLLLL
HHLHLLH
页擦除HLLLHLH
块擦除HLLLHHH

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_enboot区的使能信号/1
addr_offset地址偏移,Boot区域的首地址/5
flash_busyflash是否busy/1
flash_wr_done是否写完成/1
flash_pe_done页擦除/1
hready_flag反馈给AHB总线
flash_rdata读取的数据/32
flash_ctrl_intCPU来读取的信号,考虑CPU在看到flash_ctrl_int则会来读状态寄存器/
flash_prog_doneflash烧写是否完成/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_enflash写使能信号/1
flash_pe_en页擦除使能/1
flash_rd_en读使能/1
flash_clk时钟信号(是否考虑不同时钟)/
flash_rst_n复位信号/1
rd_inf0_selflash0的information区读选择1/01
rd_inf1_selflash1的information区读选择1/01
rd_main0_selflash0的main区读选择1/01
rd_main1_selflash1的main区读选择1/01
flash0_rd_cs是否选择flash01/01
flash1_rd_cs是否选择flash11/01
prog_infrarea0_sel写flash0的information区域选择1/01
prog_infrarea1_sel写flash1的information区域选择1/01
prog_mainarea0_sel写flash0的main区域选择1/01
prog_mainarea1_sel写flash1的main区域选择1/01
pe_main_infr_sel擦除main还是擦除information的页1/01
t_nvstr_setupnvstr信号的建立时间>5 us/12
t_nvstr_holdnvstr信号的保持时间>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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值