提示:使用正点原子达芬奇pro做的小例子,由于教程中无flash的读写,因此撰写记录
文章目录
- # N25Q128型号的spi flash驱动verilog代码编写
- 前言
- 一、阅读datasheet
- 1. 查看第4章
- 2. 查看第6章
- 3. 查看第9章
- 二、注意事项
- 1. 第一个注意的地方,就是要用STARTUPE2 的原语
- 1. 第二个需要注意的地方就是vivado想要进行debug的时候,用inout类型是不能debug的
- 总结
- 部分代码
前言
主要实现标准的spi_flash读写,以及4路quad_spi_flash读写
一、阅读datasheet通过datasheet知道他的工作时序,实现读写。
1. 查看第4章可以看到该spi有3种模式
2. 查看第6章可以看到flash状态寄存器的信息,当我们执行擦除、读写之类的命令后,有时是需要查看一下状态寄存器的,确保之前的指令执行完成后,再进行下一步的操作。
3. 查看第9章这章节就包含了读写之类的时序,根据这里的时序编写状态机就行了。9.1是传统标准的spi时序(其中也有两路和四路的spi读写),9.2是两路的,9.3是四路的
代码的编写,写了两种,一种是传统spi的,一种是四路的quad_spi的,但用的时序都是datasheet第9.1里的时序。试过用9.3的时序实现四路的spi,但读出的状态寄存器不太对头,就暂时没有尝试了。因为用9.1里的时序也能实现。
(这里代码借鉴了https://www.cnblogs.com/liujinggang/p/9651170.html)
基本上标准上spi和四路的quad_spi的工作时序和借鉴处代码相同。就配寄存器的值不太一样等一些小细节不同。所以就不列出太多代码了。
只讲几个需要注意的地方
因为外部存储器的时钟管脚一般与fpga的CCLK_0连接,这时时钟管脚已经连接到CCLK_0,但我们想要自己制造spi_flash的时钟来使用,这时STARTUPE2就派上用场了
代码如下:
//给予flash时钟
STARTUPE2 #(
.PROG_USR("FALSE"), // Activate program event security feature. Requires encrypted bitstreams.
.SIM_CCLK_FREQ(0.0) // Set the Configuration Clock Frequency(ns) for simulation
)
STARTUPE2_inst
(
.CFGCLK(), // 1-bit output: Configuration main clock output
.CFGMCLK(), // 1-bit output: Configuration internal oscillator clock output
.EOS(), // 1-bit output: Active high output signal indicating the End Of Startup.
.PREQ(), // 1-bit output: PROGRAM request to fabric output
.CLK(0), // 1-bit input: User start-up clock input
.GSR(0), // 1-bit input: Global Set/Reset input (GSR cannot be used for the port name)
.GTS(0), // 1-bit input: Global 3-state input (GTS cannot be used for the port name)
.KEYCLEARB(1), // 1-bit input: Clear AES Decrypter Key input from Battery-Backed RAM (BBRAM)
.PACK(1), // 1-bit input: PROGRAM acknowledge input
.USRCCLKO(f_clk), // 1-bit input: User CCLK input**将SPI的时钟链接到这里**
.USRCCLKTS(0), // 1-bit input: User CCLK 3-state enable input
.USRDONEO(1), // 1-bit input: User DONE pin output control
.USRDONETS(1) // 1-bit input: User DONE 3-state enable outpu
);
1. 第二个需要注意的地方就是vivado想要进行debug的时候,用inout类型是不能debug的
第二个需要注意的地方就是vivado想要进行debug的时候,用inout类型是不能debug的会报错,所以需要用iobuf原语,再对T_qspi_io0和R_qspi_io0进行debug。(T_qspi_io0相当于flash输出的数据,R_qspi_io0相当于flash输入的数据)
代码如下:
//wire T_qspi_io0;
IOBUF #(
.DRIVE(12), // Specify the output drive strength
.IBUF_LOW_PWR("TRUE"), // Low Power - "TRUE", High Performance = "FALSE"
.IOSTANDARD("DEFAULT"), // Specify the I/O standard
.SLEW("SLOW") // Specify the output slew rate
) IOBUF_inst0 (
.O(T_qspi_io0), // Buffer output
.IO(IO_qspi_io0), // Buffer inout port (connect directly to top-level port)
.I(R_qspi_io0), // Buffer input
.T(~R_qspi_io0_out_en) // 3-state enable input, high=input, low=output
);
总结
对N25Q128_flash的驱动撰写心得就是这些。
部分代码可以参考https://www.cnblogs.com/liujinggang/p/9651170.html尝试,里面讲的很好
`timescale 1ns / 1ps
module flash_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总线输入/输出信号线
//----------------------------------------------
output T_qspi_io0,
output T_qspi_io1,
output T_qspi_io2,
output T_qspi_io3,
output reg R_qspi_io0,
output reg R_qspi_io1,
output reg R_qspi_io2,
output reg R_qspi_io3,
//-----------------------------------------------
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地址
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 ; // 写数据
parameter C_FINISH_DONE = 4'b0110 ; // 一条指令执行结束
parameter C_WRITE_DATA_QUAD = 4'b1000 ; // 四线模式写数据到QSPI Flash
parameter C_DUMMY = 4'b1001 ; // 四线模式读数据需要8个时钟周期的dummy clock,这可以加快读数据的速度
parameter C_READ_WAIT_QUAD = 4'b1010 ; // 四线模式读等待状态
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 [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 ;
//wire T_qspi_io0;
IOBUF #(
.DRIVE(12), // Specify the output drive strength
.IBUF_LOW_PWR("TRUE"), // Low Power - "TRUE", High Performance = "FALSE"
.IOSTANDARD("DEFAULT"), // Specify the I/O standard
.SLEW("SLOW") // Specify the output slew rate
) IOBUF_inst0 (
.O(T_qspi_io0), // Buffer output
.IO(IO_qspi_io0), // Buffer inout port (connect directly to top-level port)
.I(R_qspi_io0), // Buffer input
.T(~R_qspi_io0_out_en) // 3-state enable input, high=input, low=output
);
//wire T_qspi_io1;
IOBUF #(
.DRIVE(12), // Specify the output drive strength
.IBUF_LOW_PWR("TRUE"), // Low Power - "TRUE", High Performance = "FALSE"
.IOSTANDARD("DEFAULT"), // Specify the I/O standard
.SLEW("SLOW") // Specify the output slew rate
) IOBUF_inst1 (
.O(T_qspi_io1), // Buffer output
.IO(IO_qspi_io1), // Buffer inout port (connect directly to top-level port)
.I(R_qspi_io1), // Buffer input
.T(~R_qspi_io1_out_en) // 3-state enable input, high=input, low=output
);
//wire T_qspi_io2;
IOBUF #(
.DRIVE(12), // Specify the output drive strength
.IBUF_LOW_PWR("TRUE"), // Low Power - "TRUE", High Performance = "FALSE"
.IOSTANDARD("DEFAULT"), // Specify the I/O standard
.SLEW("SLOW") // Specify the output slew rate
) IOBUF_inst2 (
.O(T_qspi_io2), // Buffer output
.IO(IO_qspi_io2), // Buffer inout port (connect directly to top-level port)
.I(R_qspi_io2), // Buffer input
.T(~R_qspi_io2_out_en) // 3-state enable input, high=input, low=output
);
//wire T_qspi_io3;
IOBUF #(
.DRIVE(12), // Specify the output drive strength
.IBUF_LOW_PWR("TRUE"), // Low Power - "TRUE", High Performance = "FALSE"
.IOSTANDARD("DEFAULT"), // Specify the I/O standard
.SLEW("SLOW") // Specify the output slew rate
) IOBUF_inst3 (
.O(T_qspi_io3), // Buffer output
.IO(IO_qspi_io3), // Buffer inout port (connect directly to top-level port)
.I(R_qspi_io3), // Buffer input
.T(~R_qspi_io3_out_en) // 3-state enable input, high=input, low=output
);
// 功能:用时钟的下降沿发送数据
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 ; //SPI 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 ;
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 ;
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 ; // 设置IO0为输出
R_qspi_clk_en <= 1'b1 ; // 打开SPI串行时钟SCLK的使能开关
O_qspi_cs <= 1'b0 ; // 拉低片选信号CS
if((I_cmd_type[3:0] == 4'b1000) || (I_cmd_type[3:0] == 4'b1001) )
begin
R_qspi_io3_out_en <= 1'b1; // 设置IO0为输出
end
if(R_write_bits_cnt > 0)
begin //如果R_cmd_reg还没有发送完
R_qspi_io0 <= R_cmd_reg[R_write_bits_cnt] ; //发送bit7~bit1位
R_qspi_io3 <= 1'b1;
R_write_bits_cnt <= R_write_bits_cnt-1'b1 ;
end
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 <= 2 ;//读状态寄存器指令需要接收一个数据
end
else if(I_cmd_type[3:0] == 4'b0000)//读设备ID指令(Read Device ID)
begin
O_qspi_state <= C_READ_WAIT ;
R_read_bytes_num <= 20 ; //
end
else if( (I_cmd_type[3:0] == 4'b0010) ||
(I_cmd_type[3:0] == 4'b0101) ||
(I_cmd_type[3:0] == 4'b0111) ||
(I_cmd_type[3:0] == 4'b1000) // 如果是四线模式页编程指令(Quad Page Program)
)
begin // 如果是扇区擦除(Sector Erase),页编程指令(Page Program),读数据指令(Read Data)
O_qspi_state <= C_SEND_ADDR ;
R_write_bits_cnt <= 23 ; // 这几条指令后面都需要跟一个24-bit的地址码
end
else if(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
end
end
C_SEND_ADDR: // 发送地址状态
begin
if(R_write_bits_cnt > 0) //如果R_cmd_reg还没有发送完
begin
R_qspi_io0 <= R_address_reg[R_write_bits_cnt] ; //发送bit23~bit1位
R_write_bits_cnt <= R_write_bits_cnt - 1 ;
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'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_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_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_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 ; // 单线模式下读数据标志信号,此信号为高标志正在接收数据
end
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~bit1位
R_write_bits_cnt <= R_write_bits_cnt - 1'b1 ;
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_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],T_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],T_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],T_qspi_io3,T_qspi_io2,T_qspi_io1,T_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],T_qspi_io3,T_qspi_io2,T_qspi_io1,T_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
.ena (1'b1),
.addra(W_rom_addr), // input [7 : 0] addra
.douta(W_rom_out) // output [7 : 0] douta
);
endmodule



