Verilog SPI-Flash读写总线控制模块

        此篇是我在学习中做的归纳与总结,其中如果存在版权或知识错误或问题请直接联系我,欢迎留言。
        PS:本着知识共享的原则,此篇博客可以转载,但请标明出处!

 


        SPI,顾名思义就是串行外围通信接口,只需四条线就可以完成主、从与各种外围器件全双工同步通信。4根接口线分别是:串行时钟线(SCK)、主机输入/从机输出数据线(MISO)、主机输出/从机输入(MOSI),低电平有效从机选择线(CS)。

        SPI系统可分为主机设备和从机设备两类设备,其中主机提供SPI时钟信号和片选信号,从机设备是接受SPI信号的任何集成电路。当SPI工作时,在移位寄存器中的数据逐位从输出引脚(MOSI)输出,同时从输入引脚(MISO)逐位接受数据。发送和接收操作都受控于SPI主机设备的时钟信号(SCK),从而保证同步,因此只能有一个主设备,但从设备可以有多个,通过不同的片选信号可以同时选择一个或多个从设备。

代码功能介绍:

1、数据用写片仪将数据写入SPI-Flash中【W25Q128BV】

2、使用A7系列FPGA控制SPI读取Flash中数据。

以下为SPI-Flash读取数据控制模块代码,实测有效:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2019/11/26 11:55:56
// Design Name: 
// Module Name: spi_driver
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module spi_driver(
input                   I_qspi_miso         , // SPI总线输入信号线,也是QSPI Flash的输出信号线                                     
input                   I_rst_n             , // 复位信号
input                   I_clk               , // 25MHz时钟信号
input       [4:0]       I_cmd_type          , // 命令类型
input       [7:0]       I_cmd_code          , // 命令码
input       [23:0]      I_qspi_addr         , // QSPI Flash地址

output                  O_qspi_clk          , // SPI总线串行时钟线
output reg              O_qspi_cs           , // SPI总线片选信号
output reg              O_qspi_mosi         , // SPI总线输出信号线,也是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  ; // 一条指令执行结束

reg         [7:0]   R_read_data_reg     ; // 从Flash中读出的数据用这个变量进行缓存,等读完了在把这个变量的值给输出
reg                 R_qspi_clk_en       ; // 串行时钟使能信号
reg                 R_data_come_single  ; // 单线操作读数据使能信号,当这个信号为高时
            
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       ; // 读数据结束标志位


assign O_qspi_clk = R_qspi_clk_en ? I_clk : 0   ; // 产生串行时钟信号


// 功能:用时钟的下降沿发送数据

always @(negedge I_clk) 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: begin // 初始化各个寄存器,当检测到命令类型有效(命令类型的最高位位1)以后,进入发送命令码状态                        
                R_qspi_clk_en  <=   1'b0         ;
                O_qspi_cs      <=   1'b1         ;
                O_qspi_mosi    <=   1'b0         ;    
                R_cmd_reg      <=   I_cmd_code   ;
                R_address_reg  <=   I_qspi_addr  ;
                O_done_sig     <=   1'b0         ;            
                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: begin// 发送8-bit命令码状态  
                R_qspi_clk_en       <=  1'b1    ; // 打开SPI串行时钟SCLK的使能开关
                O_qspi_cs           <=  1'b0    ; // 拉低片选信号CS
                if(R_write_bits_cnt > 0) begin  //如果R_cmd_reg还没有发送完
                    O_qspi_mosi        <=  R_cmd_reg[R_write_bits_cnt] ;         //发送bit7~bit1位
                    R_write_bits_cnt   <=  R_write_bits_cnt-1'b1       ;
                end                            
                else begin  //发送bit0
                    O_qspi_mosi <=  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) || (I_cmd_type[3:0] == 4'b0101) 
                    	  || (I_cmd_type[3:0] == 4'b0111) || (I_cmd_type[3:0] == 4'b0000) ) 
                        begin // 如果是扇区擦除(Sector Erase),页编程指令(Page Program),读数据指令(Read Data),读设备ID指令(Read Device ID)                          
                            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) begin //如果R_cmd_reg还没有发送完                            
                    O_qspi_mosi        <=  R_address_reg[R_write_bits_cnt] ; //发送bit23~bit1位
                    R_write_bits_cnt   <=  R_write_bits_cnt    -   1       ;    
                end                                 
                else begin 
                    O_qspi_mosi <=  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) begin// 页编程(Page Program)指令                           
                        O_qspi_state        <=  C_WRITE_DATA    ;
                        R_write_bits_cnt    <=  7               ;                       
                    end
                    else if (I_cmd_type[3:0] == 4'b0000) begin// 读Device ID指令       
                        O_qspi_state        <=  C_READ_WAIT     ;
                        R_read_bytes_num    <=  2               ; //接收2个数据的Device ID
                    end                                                         
                    else if (I_cmd_type[3:0] == 4'b0111) begin// 读数据(Read Data)指令 
                        O_qspi_state        <=  C_READ_WAIT     ;
                        R_read_bytes_num    <=  6             ;   //接收6个数据        
                    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) begin// 往QSPI Flash中写入 256个数据                 
                    if(R_write_bits_cnt > 0) begin//如果数据还没有发送完                  
                        O_qspi_mosi         <=  W_rom_out[R_write_bits_cnt] ; //发送bit7~bit1位
                        R_write_bits_cnt    <=  R_write_bits_cnt  - 1'b1    ;                        
                    end                 
                    else  begin                                 
                        O_qspi_mosi         <=  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    ;
                O_qspi_mosi         <=  1'b0    ;
                R_qspi_clk_en       <=  1'b0    ;
                O_done_sig          <=  1'b1    ;
                R_data_come_single  <=  1'b0    ;
                O_qspi_state        <=  C_IDLE  ;
            end
            default:O_qspi_state    <=  C_IDLE      ;
        endcase         
    end
end

//
// 功能:接收QSPI Flash发送过来的数据    
//
always @(posedge I_clk) 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) begin  // 此信号为高表示接收数据从QSPI Flash发过来的数据 
        if(R_read_bytes_cnt < R_read_bytes_num)  begin            
            if(R_read_bits_cnt < 8'd7) begin //接收6Bytes                         
                O_read_byte_valid   <=  1'b0                               ;
                R_read_data_reg     <=  {R_read_data_reg[6:0],I_qspi_miso} ;
                R_read_bits_cnt     <=  R_read_bits_cnt +   1'b1           ;
            end else  begin
                O_read_byte_valid   <=  1'b1                               ;  //6个byte数据有效
                O_read_data         <=  {R_read_data_reg[6:0],I_qspi_miso} ;  //接收bit47
                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  

endmodule

  • 3
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个简单的 Verilog 代码示例,用于通过 SPI 总线读写 Flash 存储器: ```verilog module spi_flash ( input wire clk, input wire rst, input wire [7:0] spi_data_in, output wire [7:0] spi_data_out, input wire spi_sclk, input wire spi_ss, input wire spi_mosi, output wire spi_miso, input wire spi_wp, input wire spi_hold, input wire [23:0] spi_addr, input wire spi_wren, input wire spi_rden, output wire spi_busy, output reg [15:0] spi_status ); // 定义 Flash 存储器的指令 parameter CMD_WREN = 8'h06; parameter CMD_WRDI = 8'h04; parameter CMD_RDSR = 8'h05; parameter CMD_WRSR = 8'h01; parameter CMD_READ = 8'h03; parameter CMD_FAST_READ = 8'h0B; parameter CMD_PP = 8'h02; parameter CMD_SE = 8'h20; parameter CMD_BE = 8'h52; parameter CMD_CE = 8'h60; parameter CMD_DP = 8'hB9; parameter CMD_RES = 8'hAB; parameter CMD_RDID = 8'h9F; // 定义 Flash 存储器的状态寄存器 reg [7:0] flash_sr; // 定义 SPI 总线的状态机状态 reg [3:0] spi_fsm_state; // 定义存储器的地址、数据和指令 reg [23:0] mem_addr; reg [7:0] mem_data; reg [7:0] mem_cmd; // 定义 SPI 总线的接收和发送缓冲区 reg [7:0] spi_rx_buf; reg [7:0] spi_tx_buf; // 定义计数器和标志位 reg [7:0] cnt; reg spi_busy_flag; // 定义时序参数 parameter SCK_HALF_PERIOD = 10; // SPI 时钟的半个周期的时间 // 初始化状态机状态和标志位 initial begin spi_fsm_state = 4'h0; spi_busy_flag = 1'b0; end // 状态机 always @(posedge clk) begin if (rst) begin spi_fsm_state <= 4'h0; spi_busy_flag <= 1'b0; end else begin case (spi_fsm_state) 4'h0: begin // 空闲状态 spi_busy <= 1'b0; spi_miso <= 1'b1; if (spi_ss == 1'b0) begin // SPI 片选信号被拉低,启动读写操作 spi_fsm_state <= 4'h1; spi_tx_buf <= mem_cmd; end end 4'h1: begin // 等待 Flash 存储器准备好 spi_busy <= 1'b1; spi_miso <= 1'b1; spi_tx_buf <= mem_addr[15:8]; spi_fsm_state <= 4'h2; end 4'h2: begin // 发送地址的高位 spi_busy <= 1'b1; spi_miso <= 1'b1; spi_tx_buf <= mem_addr[7:0]; spi_fsm_state <= 4'h3; end 4'h3: begin // 发送地址的低位 spi_busy <= 1'b1; spi_miso <= 1'b1; spi_tx_buf <= mem_data; spi_fsm_state <= 4'h4; end 4'h4: begin // 发送数据 spi_busy <= 1'b1; spi_miso <= 1'b1; spi_rx_buf <= spi_data_in; spi_fsm_state <= 4'h5; end 4'h5: begin // 接收数据 spi_busy <= 1'b1; spi_miso <= 1'b0; spi_tx_buf <= mem_data; spi_fsm_state <= 4'h6; end 4'h6: begin // 发送数据 spi_busy <= 1'b1; spi_miso <= 1'b1; spi_rx_buf <= spi_data_in; spi_fsm_state <= 4'h7; end 4'h7: begin // 接收数据 spi_busy <= 1'b1; spi_miso <= 1'b0; spi_tx_buf <= 8'hFF; spi_fsm_state <= 4'h8; end 4'h8: begin // 等待 Flash 存储器完成操作 spi_busy <= 1'b1; spi_miso <= 1'b1; spi_rx_buf <= spi_data_in; spi_fsm_state <= 4'h9; end 4'h9: begin // 判断是否需要发送下一个读写操作 spi_busy <= 1'b0; spi_miso <= 1'b1; spi_rx_buf <= spi_data_in; if (spi_ss == 1'b1) begin // SPI 片选信号被拉高,读写操作结束 spi_fsm_state <= 4'h0; spi_busy_flag <= 1'b0; end else begin // SPI 片选信号仍为低电平,发送下一个读写操作 spi_fsm_state <= 4'h1; mem_cmd <= spi_rx_buf; mem_addr <= {spi_rx_buf, spi_data_in}; mem_data <= spi_data_in; end end default: begin // 异常状态 spi_fsm_state <= 4'h0; spi_busy_flag <= 1'b0; end endcase end end // 定义状态机的计数器 always @(posedge clk) begin if (rst) begin cnt <= 8'h0; end else begin if (spi_fsm_state == 4'h1 || spi_fsm_state == 4'h8) begin // 等待 Flash 存储器准备或完成 if (cnt < 8'hFF) begin cnt <= cnt + 1; end else begin spi_fsm_state <= 4'h0; spi_busy_flag <= 1'b0; cnt <= 8'h0; end end else begin cnt <= 8'h0; end end end // 定义状态机的状态输出 always @(posedge clk) begin if (rst) begin spi_status <= 16'h0000; end else begin case (spi_fsm_state) 4'h0: begin // 空闲状态 spi_status <= 16'h0000; end 4'h1: begin // 等待 Flash 存储器准备好 spi_status <= 16'h0001; end 4'h2: begin // 发送地址的高位 spi_status <= 16'h0002; end 4'h3: begin // 发送地址的低位 spi_status <= 16'h0003; end 4'h4: begin // 发送数据 spi_status <= 16'h0004; end 4'h5: begin // 接收数据 spi_status <= 16'h0005; end 4'h6: begin // 发送数据 spi_status <= 16'h0006; end 4'h7: begin // 接收数据 spi_status <= 16'h0007; end 4'h8: begin // 等待 Flash 存储器完成操作 spi_status <= 16'h0008; end 4'h9: begin // 判断是否需要发送下一个读写操作 spi_status <= 16'h0009; end default: begin // 异常状态 spi_status <= 16'hFFFF; end endcase end end endmodule ``` 这个代码示例中,我们使用 Verilog 实现了一个简单的 SPI 总线接口,用于读写 Flash 存储器。在这个代码中,我们定义了 Flash 存储器的指令,并通过状态机实现了读写操作。同时,我们还定义了计数器和标志位,用于判断 Flash 存储器是否准备好,以及在读写操作完成后是否需要发送下一个读写操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虚怀若水

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值