在之前的文章中, 我们介绍了SPI 通讯协议,同时给出了一个简单的spi verilog 工程。 通过简单spi verilog 工程,我们可以对一些外设进行通讯。但简单的SPI 工程也有一些缺点。本文主要针对SPI FLASH 这种外设,搭建一个完整的verilog 工程。同时针简单spi 工程中的一些缺点,做一些补充,使得当前的工程可以完成spi flash 的相关操作。
参考文章:
SPI 通讯协议 及 SPI 相关工程 详解
在之前的简单spi 工程中, 我们实现了基本的spi 通讯,可以帮助大家迅速建立spi 通讯协议的概念,同时也可以做一些外设的应用。但如果想要操作spi flash 这样的外设, 还是有很多工作需要做的。同时,简单spi 工程 的 phy 时钟的速度会受到系统时钟的限制(速度不会很高)。 本文将制作一个全新的spi flash 工程,系统解决这些相关的问题, 同时增加spi flash 所相关的命令(指令),用于spi flash 的操作。
SPI FLASH 系统框图
整个spi flash 工程包括:
顶层模块 (top.v)
时钟模块 (pll IP)
按键模块 (key_filter.v)
spi 通讯模块 (spi_IF.v)
spi 命令模块(spi_cmd.v)
spi phy 模块(spi_phy.v)
uart 接口模块 (pc_uart.v)
uart 传输模块 (uart_transceiver.v)
组成的。
这里,我们从最顶层的spi_phy.v 模块开始,一层一层向上介绍。
spi_phy.v 模块
spi_phy.v 模块是最底层的spi 通讯接口模块, 它主要是用来完成基本的spi 相关的通讯协议。包括cs , clk, mosi, miso 等。
代码介绍
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: Fraser Innovation Inc // Engineer: WilliamG // // Create Date: 2020/06/22 09:12:51 // Design Name: // Module Name: spi_phy.v // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module spi_phy ( input phy_clk, // spi 输出时钟 input spi_cs_cmd, // 片选信号指令, input [7:0] flash_din, // 需要发送的spi 数据 input flash_din_rdy, // 需要发送的spi 数据 enable 一个时钟宽度 output reg flash_data_done = 0, // 当前数据已经发送完毕 output reg [7:0] flash_dout = 8'hff, // 读取的flash数据 output reg flash_dout_vld = 0, // flash数据的使能信号 output reg spi_cs = 1, // SPI 片选信号 output spi_clk, // SPI 时钟信号 output spi_mosi, // SPI 发送数据线 input spi_miso, // SPI 接收数据线 input rst_n ); //======================================================================================= // SPI 输出时钟使能逻辑, 当数据rdy 后,使能时钟, 当发送结束后, 停止时钟 reg clk_stop = 1; always @ (posedge phy_clk or negedge rst_n) if(!rst_n) clk_stop <= 1; else begin if(flash_data_done) clk_stop <= 1; else if(flash_din_rdy) clk_stop <= 0; end // 时钟输出选择信号, 片选有效, 并且时钟使能有效 wire spi_clk_ce = (~clk_stop); `define XILINX_FPGA `ifdef XILINX_FPGA ODDR #( .DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE" .INIT(1'b0), // Initial value of Q: 1'b0 or 1'b1 .SRTYPE("ASYNC") // Set/Reset type: "SYNC" or "ASYNC" ) SPI_CLK_ODDR_inst ( .Q (spi_clk), // 1-bit DDR output .C (~phy_clk), // 1-bit clock input .CE (spi_clk_ce), // 1-bit clock enable input .D1 (1'b1), // 1-bit data input (positive edge) .D2 (1'b0), // 1-bit data input (negative edge) .R (1'b0), // 1-bit reset .S (1'b0) // 1-bit set ); `else assign spi_clk = spi_clk_ce ? ~phy_clk : 0; `endif //======================================================================================= // 同步 negedge reset 信号 reg phy_reset_neg = 0; always @ (posedge phy_clk ) phy_reset_neg <= (!rst_n) | spi_cs_cmd; reg [7:0] flash_data_r = 8'hff; reg [2:0] tx_cnt = 0; reg tx_st = 0; always @ (posedge phy_clk) if(phy_reset_neg) begin flash_data_done <= 0; flash_data_r <= 8'hff; tx_st <= 0; tx_cnt <= 0; end else case (tx_st) 0: begin flash_data_done <= 0; tx_cnt <= 0; if(flash_din_rdy ) begin flash_data_r <= flash_din; tx_st <= 1; end end 1: begin flash_data_r <= {flash_data_r[6:0], 1'b1}; tx_cnt <= tx_cnt + 1; if(tx_cnt == 6) flash_data_done <= 1; else flash_data_done <= 0; if(tx_cnt == 7) tx_st <= 0; end endcase assign spi_mosi = flash_data_r[7]; //======================================================================================= // 同步 posedge reset 信号 reg phy_reset = 0; always @ (posedge phy_clk ) phy_reset <= (!rst_n) | flash_data_done | spi_cs_cmd; reg [3:0] rx_cnt = 0; always @ (posedge phy_clk ) if( phy_reset ) begin flash_dout <= 8'hff; flash_dout_vld <= 0; rx_cnt <= 0; end else begin rx_cnt <= rx_cnt + 1; flash_dout <= {flash_dout[6:0], spi_miso}; if(rx_cnt == 7) flash_dout_vld <= 1; if(flash_dout_vld) flash_dout_vld <= 0; end //======================================================================================= always @ (posedge phy_clk or negedge rst_n) if(!rst_n) begin spi_cs <= 1; end else begin spi_cs <= spi_cs_cmd; end //======================================================================================= endmodule |
代码介绍
|
1 |
module spi_phy<br />(<br /> input <span style="color: #0000ff;">phy_clk</span>, // spi 输出时钟<br /><br /> input <span style="color: #0000ff;">spi_cs_cmd</span>, // 片选信号指令,<br /><br /> input [7:0] <span style="color: #0000ff;">flash_din</span>, // 需要发送的spi 数据<br /> input <span style="color: #0000ff;">flash_din_rdy</span>, // 需要发送的spi 数据 enable 一个时钟宽度<br /> output reg <span style="color: #0000ff;">flash_data_done</span> = 0, // 当前数据已经发送完毕<br /><br /> output reg [7:0] <span style="color: #0000ff;">flash_dout</span> = 8'hff, // 读取的flash数据<br /> output reg <span style="color: #0000ff;">flash_dout_vld</span> = 0, // flash数据的使能信号<br /><br /> output reg <span style="color: #0000ff;">spi_cs</span> = 1, // SPI 片选信号<br /> output <span style="color: #0000ff;">spi_clk</span>, // SPI 时钟信号<br /> output <span style="color: #0000ff;">spi_mosi</span>, // SPI 发送数据线<br /> input <span style="color: #0000ff;">spi_miso</span>, // SPI 接收数据线<br /><br /> input <span style="color: #0000ff;">rst_n</span><br />); |
其中 spi_cs_cmd 是用来控制 SPI CS 信号的。
flash_din 是上层模块发送的数据(准备由spi_phy.v 发送到 mosi 上的),
flash_din_rdy 配合flash_din ,flash_din_rdy 是一个时钟宽度。
flash_data_done 信号 由spi_phy.v 模块产生, 返回给上层模块,用于通知上层模块 ,当前的数据已经发送完毕
flash_dout 信号由spi_phy.v 模块产生, 表示从miso 信号线上(其他spi 设备)接收到8bit数据, 准备送给上层模块
flash_dout_vld 信号表示当前的flash_dout 信号有效, 这个信号只有一个时钟宽度。
spi_cs 连接到其他SPI设备 的片选信号
spi_clk 连接到其他SPI设备 的时钟信号
spi_mosi 连接到其他SPI设备 的数据发送信号
spi_miso 连接到其他SPI设备 的数据接收信号
|
1 |
// SPI 输出时钟使能逻辑, 当数据rdy 后,使能时钟, 当发送结束后, 停止时钟<br />reg <span style="color: #0000ff;">clk_stop</span> = 1;<br />always @ (posedge phy_clk or negedge rst_n)<br />if(!rst_n) <span style="color: #0000ff;">clk_stop</span> <= 1;<br />else<br />begin<br /> if(flash_data_done) <span style="color: #0000ff;">clk_stop</span> <= 1;<br /> else if(flash_din_rdy) <span style="color: #0000ff;">clk_stop</span> <= 0;<br />end<br /><br /><br />// 时钟输出选择信号, 片选有效, 并且时钟使能有效<br />wire spi_clk_ce = (~clk_stop);<br /><br />`define XILINX_FPGA<br />`ifdef XILINX_FPGA<br /><br /> ODDR #(<br /> .DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE" <br /> .INIT(1'b0), // Initial value of Q: 1'b0 or 1'b1<br /> .SRTYPE("ASYNC") // Set/Reset type: "SYNC" or "ASYNC" <br /> ) SPI_CLK_ODDR_inst (<br /> .Q (spi_clk), // 1-bit DDR output<br /> .C (~phy_clk), // 1-bit clock input<br /> .CE (spi_clk_ce), // 1-bit clock enable input<br /> .D1 (1'b1), // 1-bit data input (positive edge)<br /> .D2 (1'b0), // 1-bit data input (negative edge)<br /> .R (1'b0), // 1-bit reset<br /> .S (1'b0) // 1-bit set<br /> );<br />`else<br /> assign spi_clk = spi_clk_ce ? ~phy_clk : 0;<br />`endif |
clk_stop 表示时钟停止信号, clk_stop = 1 , spi_clk 停止时钟输出; clk_stop = 0, spi_clk 正常输出。当8bit 数据发送完毕后(flash_data_done),clk_stop = 1, 时钟停止输出; 当新数据准备好(flash_din_rdy),clk_stop = 0, 时钟正常输出
接下来的逻辑中xilinx 工程使用的是 ODDR, 这个源语 实现的效果和 assign spi_clk = spi_clk_ce ? ~phy_clk : 0; 是一样的。但在xilinx 6 系列之后的芯片中, xilinx 建议使用ODDR 方式,作为时钟输出,这样可以减少外设对时钟的负载, 这样时钟不容易变形, 系统约束效果也是比较好的。
mosi 发送逻辑
|
1 |
// 同步 negedge reset 信号<br />reg phy_reset_neg = 0;<br />always @ (posedge phy_clk )<br /><span style="color: #0000ff;">phy_reset_neg</span> <= (!rst_n) | spi_cs_cmd;<br /><br /><br />reg [7:0] flash_data_r = 8'hff;<br />reg [2:0] tx_cnt = 0; <br />reg <span style="color: #0000ff;">tx_st</span> = 0;<br />always @ (posedge phy_clk)<br />if(phy_reset_neg) <br />begin<br /> flash_data_done <= 0;<br /> flash_data_r <= 8'hff;<br /> tx_st <= 0;<br /><span style="color: #0000ff;"> tx_cnt</span> <= 0;<br />end<br />else case (tx_st)<br />0:<br />begin<br /> flash_data_done <= 0;<br /> tx_cnt <= 0;<br /><br /> if(flash_din_rdy ) <br /> begin<br /> flash_data_r <= flash_din;<br /><span style="color: #0000ff;"> tx_st</span> <= 1;<br /> end<br />end<br />1:<br />begin <br /> flash_data_r <= {flash_data_r[6:0], 1'b1};<br /> tx_cnt <= tx_cnt + 1;<br /><br /> if(tx_cnt == 6) flash_data_done <= 1;<br /> else flash_data_done <= 0;<br /><br /> if(tx_cnt == 7)<br /><span style="color: #0000ff;"> tx_st</span> <= 0;<br />end<br />endcase<br /><br />assign <span style="color: #0000ff;">spi_mosi</span> = flash_data_r[7]; |
系统复位信号rst_n 和 片选有效spi_cs_cmd 这两个信号 一起生成同步rest 信号 : phy_reset_neg , 这个信号(phy_reset_neg )用来同步复位 发送状态机。
发送状态机: 有2个状态。 状态0: 接收上层模块的数据, 状态1: 用来发送当前的数据(从上层模块接收到的), 当8bit 数据发送完毕, 产生flash_data_done 信号, 用于通知上层模块当前数据发送完毕,可以接收新的数据了。(flash_data_done 和发送的最后一个bit同时产生)
miso 接收逻辑
|
1 |
// 同步 posedge reset 信号<br />reg <span style="color: #0000ff;">phy_reset</span> = 0;<br />always @ (posedge phy_clk )<br /><span style="color: #0000ff;">phy_reset</span> <= (!rst_n) | <span style="color: #0000ff;">flash_data_done</span> | <span style="color: #0000ff;">spi_cs_cmd</span>;<br /><br /><br />reg [3:0] rx_cnt = 0; <br />always @ (posedge phy_clk )<br />if( phy_reset ) <br />begin<br /> flash_dout <= 8'hff;<br /> flash_dout_vld <= 0;<br /> rx_cnt <= 0;<br />end<br />else<br />begin<br /> rx_cnt <= rx_cnt + 1;<br /><br /> flash_dout <= {flash_dout[6:0], spi_miso};<br /> if(rx_cnt == 7)<br /> flash_dout_vld <= 1;<br /><br /> if(flash_dout_vld) flash_dout_vld <= 0;<br />end |
rst_n , flash_data_done, spi_cs_cmd 信号一起产生 phy_reset 信号。 即: 发送、接收结束, 片选失效, 系统复位 这3中情况 ,都会产生 phy_reset 信号,用于复位 接收逻辑
每当时钟上升沿时 , 从spi_miso 上接收1bit 数据, 当8bit 数据接收完毕, 产生flash_dout_vld 信号,将flash_dout ,flash_dout_vld 一起送给上层模块,表示当前接收到了1byte 数据。
spi_cs 信号
|
1 |
always @ (posedge phy_clk or negedge rst_n)<br />if(!rst_n)<br />begin<br /> <span style="color: #0000ff;">spi_cs</span> <= 1;<br />end<br />else <br />begin<br /> <span style="color: #0000ff;">spi_cs</span> <= <span style="color: #0000ff;">spi_cs_cmd</span>;<br />end |
上层模块发送的spi_cs_cmd 用来驱动spi_cs 信号。
上述模块仿真:
到此, 上层模块可以控制 spi_cs 信号, 发送 8bit 数据,让spi_phy.v 模块发送到spi_mosi 上, 接收spi_miso 信号解码后的8bit 数据了


