接下来主要看看用ChipScope抓出来的时序图和芯片手册规定的时序图是否完全一致。
1、读ID指令
芯片手册指令的读ID指令时序图:
ChipScope抓出来的时序图
2、写不使能(Write Disable)指令
芯片手册指令的写不使能时序图:
ChipScope抓出来的写不使能时序图:
3、写使能(Write Enable)指令
芯片手册指令的写使能时序图:
ChipScope抓出来的写使能时序图
3、扇区擦除(Sector Erase)指令
芯片手册的扇区擦除指令时序图:
芯片手册的扇区擦除指令时序图:
4、读状态寄存器(Read Status Register)指令
芯片手册的读状态寄存器指令时序图:
ChipScope抓回来的读状态寄存器指令时序图:
由于在擦除操作和写操作(Page Program)操作指令发送完毕以后,芯片内部会自己执行相关的操作并把状态寄存器1中的最低位Busy位拉高,状态寄存器1的各位如下:
所以读状态寄存器指令的目的是为了检测Busy是否为高,Busy为高的话说明擦除操作或写操作(Page Program)操作指令并未完成,这种情况应该等待不发送其他指令,等到Busy位拉低以后说明擦除操作或写操作(Page Program)操作指令已经完成,这时才可以执行后续的操作。值得注意的是,在上图中读到8个bit数据(上图读到的数据是0000_0011(8’h03))以后,O_qpsi_cs和O_qspi_clk信号仍然持续了2个周期的时间才变为空闲状态,实际上,回过头去看代码的话很容易解释这种情况,由于代码里面是用时钟的下降沿发送数据,而用时钟的上升沿来接收数据,发送数据与接收数据的状态切换是通过R_data_coming_signal和R_read_finish信号来转换的,所以导致了这种情况的发生。但是读数据相关的指令并不要求完全8-bit对齐,如果在读数据的过程中只要O_qpsi_cs一直为低,那么会一直重复读下去,直至O_qpsi_cs拉高为止才停止读数据,所以上面多出来的两个时钟并不会影响结果的正确性。
5、写数据(Page Program)指令(单线模式)
芯片手册的写数据指令时序图:
ChipScope抓回来的时序图
6、读数据(Read Data)指令(单线模式)
芯片手册的读数据指令时序图:
ChipScope抓回来的时序图:
通过上面用ChipScope抓回来的时序图与芯片手册的时序图进行对比可以很清晰的理解整个QSPI Flash的操作流程与时序细节。
4.4、 如何处理双向信号(Verilog中用关键词inout定义的信号都是双向信号)
四线操作中IO0,IO1,IO2和IO3全部都可以用来发送数据以及接收数据,所以在编写代码的时候要把它们全部定义成双向类型的信号,即inout类型。所以在编写四线操作的代码之前有必要提前熟悉一下inout信号的处理方法。
总的来说,inout信号大致有以下4个特征:
1、inout端口不能被赋值为reg型,因此,不能用于always语句中。
2、对于inout端口的逻辑判断,要用到?:条件表达式,来控制高阻的赋值
3、inout信号需要有一个中转的寄存器,在always语句中才可以将输入的信号赋给输出(用inout代替纯output)
4、高阻态不要用于芯片内部,应该用逻辑引到引脚处,然后用高阻来实现。
实际上,FPGA内部的双向信号是通过一个三态门来实现的,一个典型的三态门结构如下
描述这个三态门的Verilog代码如下:
`module Test_inout ( input I_clk, input I_rst_n, . . . inout IO_data, . . . )
reg R_data_out ; wire I_data_in ; assign IO_data = Control ? R_data_out : 1'bz ; assign I_data_in = IO_data ;
always @(posedge I_clk or negedge I_rst_n) begin . . . ; end
endmodule`
上面的代码表达的意思是:当Control为1是,IO_data信号作为输出,输出R_data_out的值,当Control为0时,IO_data信号作为输入,输入的值赋给I_data_in变量。
值得注意的是,inout作为输出的时候不能直接给他赋值,而需要通过一个中间变量R_data_out来间接赋值,同时inout变量的输入输出属性必须通过一个控制信号Control来控制,控制性号为高时为inout信号作为输出,输出R_data_out的值,为低时inout处于高阻状态,这时才作为输入接收外部的数据。
4.5、 四线SPI总线操作QSPI Flash思路与代码编写
上一小节已经完成了单线模式QSPI Flash的读写以及其他相关操作,这一节将在上一节的基础上加上四线读写功能。通过进一步阅读W25Q128BV的芯片手册可知,在进行四线读写操作之前一定要把QE(Quad Enable)位置为1,而QE位在状态寄存器2的倒数第二位,见下图
除此以外,四线读操作还需要在读数据之前等待8个dummy clock用来加快读数据的速度(详细内容请阅读芯片手册)。所以,四线操作相对于单线操作而言除了要增加四线模式读写数据的状态以外还要增加一个写状态寄存器的功能用来开启QE(Quad Enable)位以及一个dummy用来等待8个clock。
综上所述,四线模式的状态为以下几个:
1、空闲状态:用来初始化各个寄存器的值
2、发送命令状态:用来发送8-bit的命令码
3、发送地址状态:用来发送24-bit的地址码
4、读等待状态(单线模式):当读数据操作正在进行的时候进入此状态等待读数据完毕
5、写数据状态(单线模式):在这个状态FPGA往QSPI Flash里面写数据
6、写状态寄存器状态:用来把状态寄存器的QE(Quad Enable)置1
7、Dummy Clock状态:四线读数据之前需要等待8个dummy clock
8、写数据状态(四线模式):在这个状态FPGA往QSPI Flash里面通过四线模式写数据
9、读等待状态(四线模式):在这个状态等待FPGA从QSPI Flash里面通过四线模式读数据完成
10、结束状态:一条指令操作结束,并给出一个结束标志
其中标红的状态是四线模式的代码在单线模式代码的基础上增加的四个状态。
四线模式的完整代码如下所示: ``timescale 1ns / 1ps
module qspi_driver ( output O_qspi_clk , // QSPI Flash Quad SPI(QPI)总线串行时钟线 output reg O_qspi_cs , // QPI总线片选信号 inout IO_qspi_io0 , // QPI总线输入/输出信号线 inout IO_qspi_io1 , // QPI总线输入/输出信号线 inout IO_qspi_io2 , // QPI总线输入/输出信号线 inout IO_qspi_io3 , // QPI总线输入/输出信号线
input I_rst_n , // 复位信号
input I_clk_25M , // 25MHz时钟信号 input [4:0] I_cmd_type , // 命令类型 input [7:0] I_cmd_code , // 命令码 input [23:0] I_qspi_addr , // QSPI Flash地址 input [15:0] I_status_reg , // QSPI Flash状态寄存器的值
output reg O_done_sig , // 指令执行结束标志 output reg [7:0] O_read_data , // 从QSPI Flash读出的数据 output reg O_read_byte_valid , // 读一个字节完成的标志 output reg [3:0] O_qspi_state // 状态机,用于在顶层调试用 );
parameter C_IDLE = 4'b0000 ; // 空闲状态 parameter C_SEND_CMD = 4'b0001 ; // 发送命令码 parameter C_SEND_ADDR = 4'b0010 ; // 发送地址码 parameter C_READ_WAIT = 4'b0011 ; // 单线模式读等待 parameter C_WRITE_DATA = 4'b0101 ; // 单线模式写数据到QSPI Flash parameter C_FINISH_DONE = 4'b0110 ; // 一条指令执行结束
parameter C_WRITE_STATE_REG = 4'b0111 ; // 写状态寄存器 parameter C_WRITE_DATA_QUAD = 4'b1000 ; // 四线模式写数据到QSPI Flash parameter C_DUMMY = 4'b1001 ; // 四线模式读数据需要8个时钟周期的dummy clock,这可以加快读数据的速度 parameter C_READ_WAIT_QUAD = 4'b1010 ; // 四线模式读等待状态
// QSPI Flash IO输入输出状态控制寄存器
reg R_qspi_io0 ;
reg R_qspi_io1 ;
reg R_qspi_io2 ;
reg R_qspi_io3 ;
reg R_qspi_io0_out_en ;
reg R_qspi_io1_out_en ;
reg R_qspi_io2_out_en ;
reg R_qspi_io3_out_en ;
reg [7:0] R_read_data_reg ; // 从Flash中读出的数据用这个变量进行缓存,等读完了在把这个变量的值给输出 reg R_qspi_clk_en ; // 串行时钟使能信号 reg R_data_come_single ; // 单线操作读数据使能信号,当这个信号为高时 reg R_data_come_quad ; // 单线操作读数据使能信号,当这个信号为高时
reg [7:0] R_cmd_reg ; // 命令码寄存器 reg [23:0] R_address_reg ; // 地址码寄存器 reg [15:0] R_status_reg ; // 状态寄存器
reg [7:0] R_write_bits_cnt ; // 写bit计数器,写数据之前把它初始化为7,发送一个bit就减1 reg [8:0] R_write_bytes_cnt ; // 写字节计数器,发送一个字节数据就把它加1 reg [7:0] R_read_bits_cnt ; // 写bit计数器,接收一个bit就加1 reg [8:0] R_read_bytes_cnt ; // 读字节计数器,接收一个字节数据就把它加1 reg [8:0] R_read_bytes_num ; // 要接收的数据总数 reg R_read_finish ; // 读数据结束标志位
wire [7:0] W_rom_addr ;
wire [7:0] W_rom_out ;
assign O_qspi_clk = R_qspi_clk_en ? I_clk_25M : 0 ; // 产生串行时钟信号 assign W_rom_addr = R_write_bytes_cnt ;
// QSPI IO方向控制
assign IO_qspi_io0 = R_qspi_io0_out_en ? R_qspi_io0 : 1'bz ;
assign IO_qspi_io1 = R_qspi_io1_out_en ? R_qspi_io1 : 1'bz ;
assign IO_qspi_io2 = R_qspi_io2_out_en ? R_qspi_io2 : 1'bz ;
assign IO_qspi_io3 = R_qspi_io3_out_en ? R_qspi_io3 : 1'bz ;
////////////////////////////////////////////////////////////////////////////////////////////
// 功能:用时钟的下降沿发送数据
////////////////////////////////////////////////////////////////////////////////////////////
always @(negedge I_clk_25M)
begin
if(!I_rst_n)
begin
O_qspi_cs <= 1'b1 ;
O_qspi_state <= C_IDLE ;
R_cmd_reg <= 0 ;
R_address_reg <= 0 ;
R_qspi_clk_en <= 1'b0 ; //QSPI clock输出不使能
R_write_bits_cnt <= 0 ;
R_write_bytes_cnt <= 0 ;
R_read_bytes_num <= 0 ;
R_address_reg <= 0 ;
O_done_sig <= 1'b0 ;
R_data_come_single <= 1'b0 ;
R_data_come_quad <= 1'b0 ;
end
else
begin
case(O_qspi_state)
C_IDLE: // 初始化各个寄存器,当检测到命令类型有效(命令类型的最高位位1)以后,进入发送命令码状态
begin
R_qspi_clk_en <= 1'b0 ;
O_qspi_cs <= 1'b1 ;
R_qspi_io0 <= 1'b0 ;
R_cmd_reg <= I_cmd_code ;
R_address_reg <= I_qspi_addr ;
R_status_reg <= I_status_reg ;
O_done_sig <= 1'b0 ;
R_qspi_io3_out_en <= 1'b0 ; // 设置IO_qspi_io3为高阻
R_qspi_io2_out_en <= 1'b0 ; // 设置IO_qspi_io2为高阻
R_qspi_io1_out_en <= 1'b0 ; // 设置IO_qspi_io1为高阻
R_qspi_io0_out_en <= 1'b0 ; // 设置IO_qspi_io0为高阻
if(I_cmd_type[4] == 1'b1)
begin //如果flash操作命令请求
O_qspi_state <= C_SEND_CMD ;
R_write_bits_cnt <= 7 ;
R_write_bytes_cnt <= 0 ;
R_read_bytes_num <= 0 ;
end
end
C_SEND_CMD: // 发送8-bit命令码状态
begin
R_qspi_io0_out_en <= 1'b1 ; // 设置IO_qspi_io0为输出
R_qspi_clk_en <= 1'b1 ; // 打开SPI串行时钟SCLK的使能开关
O_qspi_cs <= 1'b0 ; // 拉低片选信号CS
if(R_write_bits_cnt > 0)
begin //如果R_cmd_reg还没有发送完
R_qspi_io0 <= R_cmd_reg[R_write_bits_cnt] ; //发送bit7bit1位
R_write_bits_cnt <= R_write_bits_cnt-1'b1 ;
endbit1位
R_write_bits_cnt <= R_write_bits_cnt - 1 ;
else
begin //发送bit0
R_qspi_io0 <= R_cmd_reg[0] ;
if ((I_cmd_type[3:0] == 4'b0001) | (I_cmd_type[3:0] == 4'b0100))
begin //如果是写使能指令(Write Enable)或者写不使能指令(Write Disable)
O_qspi_state <= C_FINISH_DONE ;
end
else if (I_cmd_type[3:0] == 4'b0011)
begin //如果是读状态寄存器指令(Read Register)
O_qspi_state <= C_READ_WAIT ;
R_write_bits_cnt <= 7 ;
R_read_bytes_num <= 1 ;//读状态寄存器指令需要接收一个数据
end
else if( (I_cmd_type[3:0] == 4'b0010) || // 如果是扇区擦除(Sector Erase)
(I_cmd_type[3:0] == 4'b0101) || // 如果是页编程指令(Page Program)
(I_cmd_type[3:0] == 4'b0111) || // 如果是读数据指令(Read Data)
(I_cmd_type[3:0] == 4'b0000) || // 如果是读设备ID指令(Read Device ID)
(I_cmd_type[3:0] == 4'b1000) || // 如果是四线模式页编程指令(Quad Page Program)
(I_cmd_type[3:0] == 4'b1001) // 如果是四线模式读数据指令(Quad Read Data)
)
begin
O_qspi_state <= C_SEND_ADDR ;
R_write_bits_cnt <= 23 ; // 这几条指令后面都需要跟一个24-bit的地址码
end
else if (I_cmd_type[3:0] == 4'b0110)
begin //如果是Write Status Register
O_qspi_state <= C_WRITE_STATE_REG ;
R_write_bits_cnt <= 15 ;
end
end
end
C_WRITE_STATE_REG :
begin
R_qspi_io0_out_en <= 1'b1 ; // 设置IO0为输出
if(R_write_bits_cnt > 0)
begin //如果R_cmd_reg还没有发送完
R_qspi_io0 <= R_status_reg[R_write_bits_cnt] ; //发送bit15
end
else
begin //发送bit0
R_qspi_io0 <= R_status_reg[0] ;
O_qspi_state <= C_FINISH_DONE ;
end
end
C_SEND_ADDR: // 发送地址状态
begin
R_qspi_io0_out_en <= 1'b1 ;
if(R_write_bits_cnt > 0) //如果R_cmd_reg还没有发送完
begin
R_qspi_io0 <= R_address_reg[R_write_bits_cnt] ; //发送bit23bit1位
R_write_bits_cnt <= R_write_bits_cnt - 1 ;bit1位
R_write_bits_cnt <= R_write_bits_cnt - 1'b1 ;
end
else
begin
R_qspi_io0 <= R_address_reg[0] ; //发送bit0
if(I_cmd_type[3:0] == 4'b0010) // 扇区擦除(Sector Erase)指令
begin //扇区擦除(Sector Erase)指令发完24-bit地址码就执行结束了,所以直接跳到结束状态
O_qspi_state <= C_FINISH_DONE ;
end
else if (I_cmd_type[3:0] == 4'b0101) // 页编程(Page Program)指令,页编程指令和写数据指令是一个意思
begin
O_qspi_state <= C_WRITE_DATA ;
R_write_bits_cnt <= 7 ;
end
else if (I_cmd_type[3:0] == 4'b0000) // 读Device ID指令
begin
O_qspi_state <= C_READ_WAIT ;
R_read_bytes_num <= 2 ; //接收2个数据的Device ID
end
else if (I_cmd_type[3:0] == 4'b0111) // 读数据(Read Data)指令
begin
O_qspi_state <= C_READ_WAIT ;
R_read_bytes_num <= 256 ; //接收256个数据
end
else if (I_cmd_type[3:0] == 4'b1000)
begin //如果是四线模式页编程指令(Quad Page Program)
O_qspi_state <= C_WRITE_DATA_QUAD ;
R_write_bits_cnt <= 7 ;
end
else if (I_cmd_type[3:0] == 4'b1001)
begin //如果是四线读操作
O_qspi_state <= C_DUMMY ;
R_read_bytes_num <= 256 ; //接收256个数据
R_write_bits_cnt <= 7 ;
end
end
end
C_DUMMY: // 四线读操作之前需要等待8个dummy clock
begin
R_qspi_io3_out_en <= 1'b0 ; // 设置IO_qspi_io3为高阻
R_qspi_io2_out_en <= 1'b0 ; // 设置IO_qspi_io2为高阻
R_qspi_io1_out_en <= 1'b0 ; // 设置IO_qspi_io1为高阻
R_qspi_io0_out_en <= 1'b0 ; // 设置IO_qspi_io0为高阻
if(R_write_bits_cnt > 0)
R_write_bits_cnt <= R_write_bits_cnt - 1 ;
else
O_qspi_state <= C_READ_WAIT_QUAD ;
end
C_READ_WAIT: // 单线模式读等待状态
begin
if(R_read_finish)
begin
O_qspi_state <= C_FINISH_DONE ;
R_data_come_single <= 1'b0 ;
end
else
begin
R_data_come_single <= 1'b1 ; // 单线模式下读数据标志信号,此信号为高标志正在接收数据
R_qspi_io1_out_en <= 1'b0 ;
end
end
C_READ_WAIT_QUAD: // 四线模式读等待状态
begin
if(R_read_finish)
begin
O_qspi_state <= C_FINISH_DONE ;
R_data_come_quad <= 1'b0 ;
end
else
R_data_come_quad <= 1'b1 ;
end
C_WRITE_DATA: // 写数据状态
begin
if(R_write_bytes_cnt < 256) // 往QSPI Flash中写入 256个数据
begin
if(R_write_bits_cnt > 0) //如果数据还没有发送完
begin
R_qspi_io0 <= W_rom_out[R_write_bits_cnt] ; //发送bit7
end
else
begin
R_qspi_io0 <= W_rom_out[0] ; //发送bit0
R_write_bits_cnt <= 7 ;
R_write_bytes_cnt <= R_write_bytes_cnt + 1'b1 ;
end
end
else
begin
O_qspi_state <= C_FINISH_DONE ;
R_qspi_clk_en <= 1'b0 ;
end
end
C_WRITE_DATA_QUAD :
begin
R_qspi_io0_out_en <= 1'b1 ; // 设置IO0为输出
R_qspi_io1_out_en <= 1'b1 ; // 设置IO1为输出
R_qspi_io2_out_en <= 1'b1 ; // 设置IO2为输出
R_qspi_io3_out_en <= 1'b1 ; // 设置IO3为输出
if(R_write_bytes_cnt == 9'd256)
begin
O_qspi_state <= C_FINISH_DONE ;
R_qspi_clk_en <= 1'b0 ;
end
else
begin
if(R_write_bits_cnt == 8'd3)
begin
R_write_bytes_cnt <= R_write_bytes_cnt + 1'b1 ;
R_write_bits_cnt <= 8'd7 ;
R_qspi_io3 <= W_rom_out[3] ; // 分别发送bit3
R_qspi_io2 <= W_rom_out[2] ; // 分别发送bit2
R_qspi_io1 <= W_rom_out[1] ; // 分别发送bit1
R_qspi_io0 <= W_rom_out[0] ; // 分别发送bit0
end
else
begin
R_write_bits_cnt <= R_write_bits_cnt - 4 ;
R_qspi_io3 <= W_rom_out[R_write_bits_cnt - 0] ; // 分别发送bit7
R_qspi_io2 <= W_rom_out[R_write_bits_cnt - 1] ; // 分别发送bit6
R_qspi_io1 <= W_rom_out[R_write_bits_cnt - 2] ; // 分别发送bit5
R_qspi_io0 <= W_rom_out[R_write_bits_cnt - 3] ; // 分别发送bit4
end
end
end
C_FINISH_DONE:
begin
O_qspi_cs <= 1'b1 ;
R_qspi_io0 <= 1'b0 ;
R_qspi_clk_en <= 1'b0 ;
O_done_sig <= 1'b1 ;
R_qspi_io3_out_en <= 1'b0 ; // 设置IO_qspi_io3为高阻
R_qspi_io2_out_en <= 1'b0 ; // 设置IO_qspi_io2为高阻
R_qspi_io1_out_en <= 1'b0 ; // 设置IO_qspi_io1为高阻
R_qspi_io0_out_en <= 1'b0 ; // 设置IO_qspi_io0为高阻
R_data_come_single <= 1'b0 ;
R_data_come_quad <= 1'b0 ;
O_qspi_state <= C_IDLE ;
end
default:O_qspi_state <= C_IDLE ;
endcase
end
end
//////////////////////////////////////////////////////////////////////////////
// 功能:接收QSPI Flash发送过来的数据
//////////////////////////////////////////////////////////////////////////////
always @(posedge I_clk_25M)
begin
if(!I_rst_n)
begin
R_read_bytes_cnt <= 0 ;
R_read_bits_cnt <= 0 ;
R_read_finish <= 1'b0 ;
O_read_byte_valid <= 1'b0 ;
R_read_data_reg <= 0 ;
O_read_data <= 0 ;
end
else if(R_data_come_single) // 此信号为高表示接收数据从QSPI Flash发过来的数据
begin
if(R_read_bytes_cnt < R_read_bytes_num)
begin
if(R_read_bits_cnt < 7) //接收一个Byte的bit0~bit6
begin
O_read_byte_valid <= 1'b0 ;
R_read_data_reg <= {R_read_data_reg[6:0],IO_qspi_io1} ;
R_read_bits_cnt <= R_read_bits_cnt + 1'b1 ;
end
else
begin
O_read_byte_valid <= 1'b1 ; //一个byte数据有效
O_read_data <= {R_read_data_reg[6:0],IO_qspi_io1} ; //接收bit7
R_read_bits_cnt <= 0 ;
R_read_bytes_cnt <= R_read_bytes_cnt + 1'b1 ;
end
end
else
begin
R_read_bytes_cnt <= 0 ;
R_read_finish <= 1'b1 ;
O_read_byte_valid <= 1'b0 ;
end
end
else if(R_data_come_quad)
begin
if(R_read_bytes_cnt < R_read_bytes_num)
begin //接收数据
if(R_read_bits_cnt < 8'd1)
begin
O_read_byte_valid <= 1'b0 ;
R_read_data_reg <= {R_read_data_reg[3:0],IO_qspi_io3,IO_qspi_io2,IO_qspi_io1,IO_qspi_io0};//接收前四位
R_read_bits_cnt <= R_read_bits_cnt + 1 ;
end
else
begin
O_read_byte_valid <= 1'b1 ;
O_read_data <= {R_read_data_reg[3:0],IO_qspi_io3,IO_qspi_io2,IO_qspi_io1,IO_qspi_io0}; //接收后四位
R_read_bits_cnt <= 0 ;
R_read_bytes_cnt <= R_read_bytes_cnt + 1'b1 ;
end
end
else
begin
R_read_bytes_cnt <= 0 ;
R_read_finish <= 1'b1 ;
O_read_byte_valid <= 1'b0 ;
end
end
else
begin
R_read_bytes_cnt <= 0 ;
R_read_bits_cnt <= 0 ;
R_read_finish <= 1'b0 ;
O_read_byte_valid <= 1'b0 ;
R_read_data_reg <= 0 ;
end
end
rom_data rom_data_inst ( .clka(I_clk_25M), // input clka .addra(W_rom_addr), // input [7 : 0] addra .douta(W_rom_out) // output [7 : 0] douta );
endmodule`
顶层测试状态机的完整代码如下:
`module qspi_top ( input I_clk , input I_rst_n ,
output O_qspi_clk , // QPI总线串行时钟线
output O_qspi_cs , // QPI总线片选信号
inout IO_qspi_io0 , // QPI总线输入/输出信号线
inout IO_qspi_io1 , // QPI总线输入/输出信号线
inout IO_qspi_io2 , // QPI总线输入/输出信号线
inout IO_qspi_io3 // QPI总线输入/输出信号线
);
reg [3:0] R_state ; reg [7:0] R_flash_cmd ; reg [23:0] R_flash_addr ; reg R_clk_25M ; reg [4:0] R_cmd_type ; reg [15:0] R_status_reg ;
wire W_done_sig ; wire [7:0] W_read_data ; wire W_read_byte_valid ; wire [2:0] R_qspi_state ;
////////////////////////////////////////////////////////////////////
//功能:二分频逻辑
////////////////////////////////////////////////////////////////////
always @(posedge I_clk or negedge I_rst_n)
begin
if(!I_rst_n)
R_clk_25M <= 1'b0 ;
else
R_clk_25M <= ~R_clk_25M ;
end
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
//功能:测试状态机
////////////////////////////////////////////////////////////////////
always @(posedge R_clk_25M or negedge I_rst_n)
begin
if(!I_rst_n)
begin
R_state <= 4'd0 ;
R_flash_addr <= 24'd0 ;
R_flash_cmd <= 8'h00 ;
R_cmd_type <= 5'b0_0000 ;
end
else
begin
case(R_state)
4'd0://读Device ID指令
begin
if(W_done_sig)
begin
R_flash_cmd <= 8'h00 ;
R_state <= R_state + 1'b1 ;
R_cmd_type <= 5'b0_0000 ;
end
else
begin
R_flash_cmd <= 8'h90 ;
R_flash_addr <= 24'd0 ;
R_cmd_type <= 5'b1_0000 ;
end
end
4'd1://写不使能(Write disable)指令
begin
if(W_done_sig)
begin
R_flash_cmd <= 8'h00 ;
R_state <= R_state + 1'b1 ;
R_cmd_type <= 5'b0_0000 ;
end
else
begin
R_flash_cmd <= 8'h04 ;
R_cmd_type <= 5'b1_0100 ;
end
end
4'd2://写使能(Write Enable)指令
begin
if(W_done_sig)
begin
R_flash_cmd <= 8'h00 ;
R_state <= R_state + 1'b1 ;
R_cmd_type <= 5'b0_0000 ;
end
else
begin
R_flash_cmd <= 8'h06 ;
R_cmd_type <= 5'b1_0001 ;
end
end
4'd3:// 扇区擦除(Sector Erase)指令
begin
if(W_done_sig)
begin
R_flash_cmd <= 8'h00 ;
R_state <= R_state + 1'b1 ;
R_cmd_type <= 5'b0_0000 ;
end
else
begin
R_flash_cmd <= 8'h20 ;
R_flash_addr<= 24'd0 ;
R_cmd_type <= 5'b1_0010 ;
end
end
4'd4://读状态寄存器1, 当Busy位(状态寄存器1的最低位)为0时表示擦除操作完成
begin
if(W_done_sig)
begin
if(W_read_data[0]==1'b0)
begin
R_flash_cmd <= 8'h00 ;
R_state <= R_state + 1'b1 ;
R_cmd_type <= 5'b0_0000 ;
end
else
begin
R_flash_cmd <= 8'h05 ;
R_cmd_type <= 5'b1_0011 ;
end
end
else
begin
R_flash_cmd <= 8'h05 ;
R_cmd_type <= 5'b1_0011 ;
end
end
4'd5://写状态寄存器2(Write Status Register2)指令,用来把QE(Quad Enable)位置1
begin
if(W_done_sig)
begin
R_flash_cmd <= 8'h00 ;
R_state <= R_state + 1'b1 ;
R_cmd_type <= 5'b0_0000 ;
end
else
begin
R_flash_cmd <= 8'h01 ;
R_cmd_type <= 5'b1_0110 ;
R_status_reg<= 16'h0002 ;
end
end
4'd6://写使能(Write Enable)指令
begin
if(W_done_sig)
begin
R_flash_cmd <= 8'h00 ;
R_state <= R_state + 1'b1 ;
R_cmd_type <= 5'b0_0000 ;
end
else
begin
R_flash_cmd <= 8'h06 ;
R_cmd_type <= 5'b1_0001 ;
end
end
4'd7: //四线模式页编程操作(Quad Page Program): 把存放在ROM中的数据写入QSPI Flash中
begin
if(W_done_sig)
begin
R_flash_cmd <= 8'h00 ;
R_state <= R_state + 1'b1 ;
R_cmd_type <= 5'b0_0000 ;
end
else
begin
R_flash_cmd <= 8'h32 ;
R_flash_addr<= 24'd0 ;
R_cmd_type <= 5'b1_1000 ;
end
end
4'd8://读状态寄存器1, 当Busy位(状态寄存器1的最低位)为0时表示写操作完成
begin
if(W_done_sig)
begin
if(W_read_data[0]==1'b0)
begin
R_flash_cmd <= 8'h00 ;
R_state <= R_state + 1'b1 ;
R_cmd_type <= 5'b0_0000 ;
end
else
begin
R_flash_cmd <= 8'h05 ;
R_cmd_type <= 5'b1_0011 ;
end
end
else
begin
R_flash_cmd <= 8'h05 ;
R_cmd_type <= 5'b1_0011 ;
end
end
4'd9://四线模式读256 Bytes数据
begin
if(W_done_sig)
begin
R_flash_cmd <= 8'h00 ;
R_state <= R_state + 1'b1 ;
R_cmd_type <= 5'b0_0000 ;
end
else
begin
R_flash_cmd <= 8'h6b ;
R_flash_addr<= 24'd0 ;
R_cmd_type <= 5'b1_1001 ;
end
end
4'd10:// 结束状态
begin
R_flash_cmd <= 8'h00 ;
R_state <= 4'd10 ;
R_cmd_type <= 5'b0_0000 ;
end
default : R_state <= 4'd0 ;
endcase
end
end qspi_driver U_qspi_driver ( .O_qspi_clk (O_qspi_clk ), // QPI总线串行时钟线 .O_qspi_cs (O_qspi_cs ), // QPI总线片选信号 .IO_qspi_io0 (IO_qspi_io0 ), // QPI总线输入/输出信号线 .IO_qspi_io1 (IO_qspi_io1 ), // QPI总线输入/输出信号线 .IO_qspi_io2 (IO_qspi_io2 ), // QPI总线输入/输出信号线 .IO_qspi_io3 (IO_qspi_io3 ), // QPI总线输入/输出信号线
.I_rst_n (I_rst_n ), // 复位信号
.I_clk_25M (R_clk_25M ), // 25MHz时钟信号 .I_cmd_type (R_cmd_type ), // 命令类型 .I_cmd_code (R_flash_cmd ), // 命令码 .I_qspi_addr (R_flash_addr ), // QSPI Flash地址 .I_status_reg (R_status_reg ), // QSPI Flash状态寄存器
.O_done_sig (W_done_sig ), // 指令执行结束标志 .O_read_data (W_read_data ), // 从QSPI Flash读出的数据 .O_read_byte_valid (W_read_byte_valid ), // 读一个字节完成的标志 .O_qspi_state (R_qspi_state ) // 状态机,用于在顶层调试用 );
wire [35:0] CONTROL0 ; wire [99:0] TRIG0 ; icon icon_inst ( .CONTROL0(CONTROL0) // INOUT BUS [35:0] );
ila ila_inst ( .CONTROL(CONTROL0) , // INOUT BUS [35:0] .CLK(I_clk) , // IN .TRIG0(TRIG0) // IN BUS [255:0] );
assign TRIG0[7:0] = W_read_data ;
assign TRIG0[8] = W_read_byte_valid ;
assign TRIG0[12:9] = R_state ;
assign TRIG0[16:13] = R_qspi_state ;
assign TRIG0[17] = W_done_sig ;
assign TRIG0[18] = IO_qspi_io0 ;
assign TRIG0[19] = IO_qspi_io1 ;
assign TRIG0[20] = IO_qspi_io2 ;
assign TRIG0[21] = IO_qspi_io3 ;
assign TRIG0[22] = O_qspi_cs ;
assign TRIG0[23] = O_qspi_clk ;
assign TRIG0[28:24] = R_cmd_type ;
assign TRIG0[36:29] = R_flash_cmd ;
assign TRIG0[60:37] = R_flash_addr ;
assign TRIG0[61] = I_rst_n ;
assign TRIG0[77:62] = R_status_reg ;
endmodule`
接下来就对比一下各个指令的时序图,由于有一部分时序图在单线模式已经对比过了,这里只对比写状态寄存器,四线读操作,四线写操作三个指令的时序图
1、写写状态寄存器(Write Status Register)指令
芯片手册写状态寄存器时序图:
ChipScope抓出来的写状态寄存器时序图:
2、四线写数据(Quad Input Page Program)指令
芯片手册四线写数据时序图:
ChipScope抓回来的四线写数据时序图:
3、四线读数据(Quad Read Data)指令
芯片手册四线读数据(Fast Read Quad Output)时序图:
ChipScope抓回来的四线读数据(Fast Read Quad Output)时序图:
通过对比各个指令与芯片手册的指令,可以发现各个指令的时序与芯片手册完全吻合,至此四线SPI操作QSPI Flash的代码全部测试通过。你可以把ROM里面的数据换成你自己的数据发给QSPI Flash,ROM里面数据则可以通过4.2小节的Matlab自定义产生。
4.6、 四线模式与单线模式读写数据时的性能对比
由于QSPI Flash四线模式中共有四根信号线,它们全部可以用做输入输出来传输数据,所以传输同样大小的数据块时候,四线模式的速度是单线模式的四倍。下面列出我在最初调试QSPI Flash的时候抓出来的两张图,分别是四线模式与单线模式写数据对比图与 四线模式与单线模式读数据对比图
四线模式与单线模式写数据对比图:
四线模式与单线模式读数据对比图:
从上面可以很清晰的看出读写同一块数据的时候,四线模式相较于单线模式而言,速度有了4倍的提升。
五、 进一步思考
5.1、 用Verilog编写QSPI Flash驱动的意义何在?
事实上,Xilinx的FPGA的已经有专门的引脚用来与QSPI Flash相连,当我们把.bin文件或者.mcs文件通过Jtag固化到QSPI Flash中的时候,ISE软件中的iMPACT会自动把.bin文件或者.mcs文件以SPI协议的格式写入到QSPI Flash中,由于QSPI Flash是一种非易失性存储器,所以断电后,数据并不会丢失,当FPGA断电重启以后,它会自动从QSPI Flash中加载事先固化到QSPI Flash里面的程序到内部的RAM中执行,而不像.bit掉电程序就会丢失。既然FPGA已经支持QSPI Flash的协议,为什么还要写QSPI Flash的驱动呢?原因是在实际的项目中,如果要远程对FPGA的代码进行在线升级的话就必须有一套QSPI Flash的驱动来配合CPU进行升级操作。
比如,某厂家生产了几千台4G基站,而且已经发货到国外各个国家商用。而后期研发人员调试的时候发现FPGA部分有一个小Bug需要修复或者说需要增加一个新功能,代码写好了以后如果没有在线升级的功能,你只能一个一个去用jtag重新固化程序,这样会耗费大量的人力成本而且极其不方便,而如果有在线升级的功能只需要通过远程登录连接到CPU上发送几条指令就可以完成升级功能,大大提高了工作效率。
5.2、 关于在代码中同时使用时钟的上升沿和下降沿操作时有什么风险
关于这个问题目前还没有找到比较权威的说法,以下两个说法是提的最多的:
1、FPGA内部的触发器都是上升沿触发的,所以,如果在代码中用下降沿触发的话会在时钟引脚的前面综合出一个反相器,这个反相器可能会对时钟信号的质量有影响
2、集成电路设计的书中有对晶振的描述,外部晶振产生的时钟信号上升沿的时间几乎是一样的,但是每个周期的下降沿的时间却无法保证完全一致,这与工艺有很大关系,所以采用下降沿触发有可能存在风险。