2.2 SPI協議的FPGA實現(二)SPI協議的FPGA實現

  下面我將以模式0爲例用Verilog編寫SPI通信的代碼。編寫SPI通信的Verilog代碼並利用ModelSim進行時序仿真
  Verilog編寫的SPI模塊除了進行SPI通信的四根線以外還要包括一些時鐘、復位、使能、並行的輸入輸出以及完成標誌位。其框圖如下所示:
在這裏插入圖片描述
            圖 2 29 SPI協議建模框圖
其中:

I_clk是系統時鐘;
I_rst_n是系統復位;
I_tx_en是主機給從機發送數據的使能信號,當I_tx_en爲1時主機才能給從機發送數據;
I_rx _en是主機從從機接收數據的使能信號,當I_rx_en爲1時主機才能從從機接收數據;
I_data_in是主機要發送的並行數據;
O_data_out是把從機接收回來的串行數據並行化以後的並行數據;
O_tx_done是主機給從機發送數據完成的標誌位,發送完成後會產生一個高脈衝;
O_rx_done是主機從從機接收數據完成的標誌位,接收完成後會產生一個高脈衝;
I_spi_miso、O_spi_cs、O_spi_sck和O_spi_mosi是標準SPI總線協議規定的四根線;

  要想實現上文模式0的時序,最簡單的辦法還是設計一個狀態機。爲了方便說明,這裏把模式0的時序再在下面貼一遍
在這裏插入圖片描述
                圖2 30 模式0下的SPI時序圖
  發送:當FPGA通過SPI總線往QSPI Flash中發送一個字節(8-bit)的數據時,首先FPGA把CS/SS片選信號設置爲0,表示準備開始發送數據,整個發送數據過程其實可以分爲16個狀態:

  • 狀態0:SCK爲0,MOSI爲要發送的數據的最高位,即I_data_in[7]
  • 狀態1:SCK爲1,MOSI保持不變
  • 狀態2:SCK爲0,MOSI爲要發送的數據的次高位,即I_data_in[6]
  • 狀態3:SCK爲1,MOSI保持不變
  • 狀態4:SCK爲0,MOSI爲要發送的數據的下一位,即I_data_in[5]
  • 狀態5:SCK爲1,MOSI保持不變
  • 狀態6:SCK爲0,MOSI爲要發送的數據的下一位,即I_data_in[4]
  • 狀態7:SCK爲1,MOSI保持不變
  • 狀態8:SCK爲0,MOSI爲要發送的數據的下一位,即I_data_in[3]
  • 狀態9:SCK爲1,MOSI保持不變
  • 狀態10:SCK爲0,MOSI爲要發送的數據的下一位,即I_data_in[2]
  • 狀態11:SCK爲1,MOSI保持不變
  • 狀態12:SCK爲0,MOSI爲要發送的數據的下一位,即I_data_in[1]
  • 狀態13:SCK爲1,MOSI保持不變
  • 狀態14:SCK爲0,MOSI爲要發送的數據的最低位,即I_data_in[0]
  • 狀態15:SCK爲1,MOSI保持不變

  一個字節數據發送完畢以後,產生一個發送完成標誌位O_tx_done並把CS/SS信號拉高完成一次發送。通過觀察上面的狀態可以發現狀態編號爲奇數的狀態要做的操作實際上是一模一樣的,所以寫代碼的時候爲了精簡代碼,可以把狀態號爲奇數的狀態全部整合到一起。
  接收:當FPGA通過SPI總線從QSPI Flash中接收一個字節(8-bit)的數據時,首先FPGA把CS/SS片選信號設置爲0,表示準備開始接收數據,整個接收數據過程其實也可以分爲16個狀態,但是與發送過程不同的是,爲了保證接收到的數據準確,必須在數據的正中間採樣,也就是說模式0時序圖中灰色實線的地方纔是代碼中鎖存數據的地方,所以接收過程的每個狀態執行的操作爲:

  • 狀態0:SCK爲0,不鎖存MISO上的數據
  • 狀態1:SCK爲1,鎖存MISO上的數據,即把MISO上的數據賦值給O_data_out[7]
  • 狀態2:SCK爲0,不鎖存MISO上的數據
  • 狀態4:SCK爲0,不鎖存MISO上的數據
  • 狀態5:SCK爲1,鎖存MISO上的數據,即把MISO上的數據賦值給O_data_out[5]
  • 狀態6:SCK爲0,不鎖存MISO上的數據
  • 狀態7:SCK爲1,鎖存MISO上的數據,即把MISO上的數據賦值給O_data_out[4]
  • 狀態8:SCK爲0,不鎖存MISO上的數據
  • 狀態9:SCK爲1,鎖存MISO上的數據,即把MISO上的數據賦值給O_data_out[3]
  • 狀態10:SCK爲0,不鎖存MISO上的數據
  • 狀態11:SCK爲1,鎖存MISO上的數據,即把MISO上的數據賦值給O_data_out[2]
  • 狀態12:SCK爲0,不鎖存MISO上的數據
  • 狀態13:SCK爲1,鎖存MISO上的數據,即把MISO上的數據賦值給O_data_out[1]
  • 狀態14:SCK爲0,不鎖存MISO上的數據
  • 狀態15:SCK爲1,鎖存MISO上的數據,即把MISO上的數據賦值給O_data_out[0]

  一個字節數據接收完畢以後,產生一個接收完成標誌位O_rx_done並把CS/SS信號拉高完成一次數據的接收。通過觀察上面的狀態可以發現狀態編號爲偶數的狀態要做的操作實際上是一模一樣的,所以寫代碼的時候爲了精簡代碼,可以把狀態號爲偶數的狀態全部整合到一起。而這一點剛好與發送過程的狀態剛好相反。
思路理清楚以後就可以直接編寫Verilog代碼了,spi_module模塊的代碼如下:
              代碼 2 9 spi_module模塊的代碼

1.	//****************************************************************************//  
2.	//# @Author: 碎碎思  
3.	//# @Date:   2019-04-18 20:57:46  
4.	//# @Last Modified by:   zlk  
5.	//# @WeChat Official Account: OpenFPGA  
6.	//# @Last Modified time: 2019-04-18 21:52:43  
7.	//# Description:   
8.	//# @Modification History: 2019-04-18 21:52:43  
9.	//# Date                By             Version             Change Description:   
10.	//# ========================================================================= #  
11.	//# 2019-04-18 21:52:43  
12.	//# ========================================================================= #  
13.	//# |                                                                       | #  
14.	//# |                                OpenFPGA                               | #  
15.	//****************************************************************************//  
16.	`timescale 1ns/ 1ps  
17.	module spi  
18.	(  
19.	    input CLOCK, RESET,// 全局時鐘50MHz/復位信號,低電平有效  
20.	    input               I_rx_en     , // 讀使能信號  
21.	    input               I_tx_en     , // 發送使能信號  
22.	    input        [7:0]  iData   , // 要發送的數據  
23.	    output  reg  [7:0]  oData  , // 接收到的數據  
24.	    output  reg         iDone   , // 發送一個字節完畢標誌位  
25.	    output  reg         oDone   , // 接收一個字節完畢標誌位  
26.	      
27.	    // 四線標準SPI信號定義  
28.	    input               I_spi_miso  , // SPI串行輸入,用來接收從機的數據  
29.	    output  reg         O_spi_sck   , // SPI時鐘  
30.	    output  reg         O_spi_cs    , // SPI片選信號  
31.	    output  reg         O_spi_mosi    // SPI輸出,用來給從機發送數據  
32.	);  
33.	//****************************************//  
34.	  
35.	reg [3:0]   R_tx_state      ;   
36.	reg [3:0]   R_rx_state      ;  
37.	  
38.	//****************************************//  
39.	always @(posedge CLOCK or negedge RESET)  
40.	begin  
41.	    if(!RESET)  
42.	        begin  
43.	            R_tx_state  <=  4'd0    ;  
44.	            R_rx_state  <=  4'd0    ;  
45.	            O_spi_cs    <=  1'b1    ;  
46.	            O_spi_sck   <=  1'b0    ;  
47.	            O_spi_mosi  <=  1'b0    ;  
48.	            iDone   <=  1'b0    ;  
49.	            oDone   <=  1'b0    ;  
50.	            oData  <=  8'd0    ;  
51.	        end   
52.	    else if(I_tx_en) // 發送使能信號打開的情況下  
53.	        begin  
54.	            O_spi_cs    <=  1'b0    ; // 把片選CS拉低  
55.	            case(R_tx_state)  
56.	                4'd1, 4'd3 , 4'd5 , 4'd7  ,   
57.	                4'd9, 4'd11, 4'd13, 4'd15 : //整合奇數狀態  
58.	                    begin  
59.	                        O_spi_sck   <=  1'b1                ;  
60.	                        R_tx_state  <=  R_tx_state + 1'b1   ;  
61.	                        iDone   <=  1'b0                ;  
62.	                    end  
63.	                4'd0:    // 發送第7位  
64.	                    begin  
65.	                        O_spi_mosi  <=  iData[7]        ;  
66.	                        O_spi_sck   <=  1'b0                ;  
67.	                        R_tx_state  <=  R_tx_state + 1'b1   ;  
68.	                        iDone   <=  1'b0                ;  
69.	                    end  
70.	                4'd2:    // 發送第6位  
71.	                    begin  
72.	                        O_spi_mosi  <=  iData[6]        ;  
73.	                        O_spi_sck   <=  1'b0                ;  
74.	                        R_tx_state  <=  R_tx_state + 1'b1   ;  
75.	                        iDone   <=  1'b0                ;  
76.	                    end  
77.	                4'd4:    // 發送第5位  
78.	                    begin  
79.	                        O_spi_mosi  <=  iData[5]        ;  
80.	                        O_spi_sck   <=  1'b0                ;  
81.	                        R_tx_state  <=  R_tx_state + 1'b1   ;  
82.	                        iDone   <=  1'b0                ;  
83.	                    end   
84.	                4'd6:    // 發送第4位  
85.	                    begin  
86.	                        O_spi_mosi  <=  iData[4]        ;  
87.	                        O_spi_sck   <=  1'b0                ;  
88.	                        R_tx_state  <=  R_tx_state + 1'b1   ;  
89.	                        iDone   <=  1'b0                ;  
90.	                    end   
91.	                4'd8:    // 發送第3位  
92.	                    begin  
93.	                        O_spi_mosi  <=  iData[3]        ;  
94.	                        O_spi_sck   <=  1'b0                ;  
95.	                        R_tx_state  <=  R_tx_state + 1'b1   ;  
96.	                        iDone   <=  1'b0                ;  
97.	                    end                              
98.	                4'd10:    // 發送第2位  
99.	                    begin  
100.	                        O_spi_mosi  <=  iData[2]        ;  
101.	                        O_spi_sck   <=  1'b0                ;  
102.	                        R_tx_state  <=  R_tx_state + 1'b1   ;  
103.	                        iDone   <=  1'b0                ;  
104.	                    end   
105.	                4'd12:    // 發送第1位  
106.	                    begin  
107.	                        O_spi_mosi  <=  iData[1]        ;  
108.	                        O_spi_sck   <=  1'b0                ;  
109.	                        R_tx_state  <=  R_tx_state + 1'b1   ;  
110.	                        iDone   <=  1'b0                ;  
111.	                    end   
112.	                4'd14:    // 發送第0位  
113.	                    begin  
114.	                        O_spi_mosi  <=  iData[0]        ;  
115.	                        O_spi_sck   <=  1'b0                ;  
116.	                        R_tx_state  <=  R_tx_state + 1'b1   ;  
117.	                        iDone   <=  1'b1                ;  
118.	                    end  
119.	                default:R_tx_state  <=  4'd0                ;     
120.	            endcase   
121.	        end  
122.	    else if(I_rx_en) // 接收使能信號打開的情況下  
123.	        begin  
124.	            O_spi_cs    <=  1'b0        ; // 拉低片選信號CS  
125.	            case(R_rx_state)  
126.	                4'd0, 4'd2 , 4'd4 , 4'd6  ,  4'd8, 4'd10, 4'd12, 4'd14 : //整合偶數狀態  
127.	                    begin  
128.	                        O_spi_sck   <=  1'b0;  
129.	                        R_rx_state  <=  R_rx_state + 1'b1;  
130.	                        oDone   <=  1'b0;  
131.	                    end  
132.	                4'd1:    // 接收第7位  
133.	                    begin                         
134.	                        O_spi_sck       <=  1'b1                ;  
135.	                        R_rx_state      <=  R_rx_state + 1'b1   ;  
136.	                        oDone       <=  1'b0                ;  
137.	                        oData[7]   <=  I_spi_miso          ;     
138.	                    end  
139.	                4'd3:    // 接收第6位  
140.	                    begin  
141.	                        O_spi_sck       <=  1'b1                ;  
142.	                        R_rx_state      <=  R_rx_state + 1'b1   ;  
143.	                        oDone       <=  1'b0                ;  
144.	                        oData[6]   <=  I_spi_miso          ;   
145.	                    end  
146.	                4'd5:    // 接收第5位  
147.	                    begin  
148.	                        O_spi_sck       <=  1'b1                ;  
149.	                        R_rx_state      <=  R_rx_state + 1'b1   ;  
150.	                        oDone       <=  1'b0                ;  
151.	                        oData[5]   <=  I_spi_miso          ;   
152.	                    end   
153.	                4'd7:    // 接收第4位  
154.	                    begin  
155.	                        O_spi_sck       <=  1'b1                ;  
156.	                        R_rx_state      <=  R_rx_state + 1'b1   ;  
157.	                        oDone       <=  1'b0                ;  
158.	                        oData[4]   <=  I_spi_miso          ;   
159.	                    end   
160.	                4'd9:    // 接收第3位  
161.	                    begin  
162.	                        O_spi_sck       <=  1'b1                ;  
163.	                        R_rx_state      <=  R_rx_state + 1'b1   ;  
164.	                        oDone       <=  1'b0                ;  
165.	                        oData[3]   <=  I_spi_miso          ;   
166.	                    end                              
167.	                4'd11:    // 接收第2位  
168.	                    begin  
169.	                        O_spi_sck       <=  1'b1                ;  
170.	                        R_rx_state      <=  R_rx_state + 1'b1   ;  
171.	                        oDone       <=  1'b0                ;  
172.	                        oData[2]   <=  I_spi_miso          ;   
173.	                    end   
174.	                4'd13:    // 接收第1位  
175.	                    begin  
176.	                        O_spi_sck       <=  1'b1                ;  
177.	                        R_rx_state      <=  R_rx_state + 1'b1   ;  
178.	                        oDone       <=  1'b0                ;  
179.	                        oData[1]   <=  I_spi_miso          ;   
180.	                    end   
181.	                4'd15:    // 接收第0位  
182.	                    begin  
183.	                        O_spi_sck       <=  1'b1                ;  
184.	                        R_rx_state      <=  R_rx_state + 1'b1   ;  
185.	                        oDone       <=  1'b1                ;  
186.	                        oData[0]   <=  I_spi_miso          ;   
187.	                    end  
188.	                default:R_rx_state  <=  4'd0                    ;     
189.	            endcase   
190.	        end      
191.	    else  
192.	        begin  
193.	            R_tx_state  <=  4'd0    ;  
194.	            R_rx_state  <=  4'd0    ;  
195.	            iDone   <=  1'b0    ;  
196.	            oDone   <=  1'b0    ;  
197.	            O_spi_cs    <=  1'b1    ;  
198.	            O_spi_sck   <=  1'b0    ;  
199.	            O_spi_mosi  <=  1'b0    ;  
200.	            oData  <=  8'd0    ;  
201.	        end        
202.	end  
203.	  
204.	endmodule  

  整個代碼的流程與之前分析的流程完全一致。接下來就對這個代碼用ModelSim進行基本的仿真。由於接收部分不再硬件上不太好測,所以這裏只對發送部分進行測試,接收部分等把代碼下載到板子裏面以後用ChipScope抓接收部分時序就一清二楚了。
發射部分的測試激勵代碼如下:
                  代碼 2 10 spi_module仿真代碼

1.	`timescale 1ns / 1ps  
2.	  
3.	module tb_spi_module;  
4.	  
5.	    // Inputs  
6.	    reg I_clk;  
7.	    reg I_rst_n;  
8.	    reg I_rx_en;  
9.	    reg I_tx_en;  
10.	    reg [7:0] I_data_in;  
11.	    reg I_spi_miso;  
12.	  
13.	    // Outputs  
14.	    wire [7:0] O_data_out;  
15.	    wire O_tx_done;  
16.	    wire O_rx_done;  
17.	    wire O_spi_sck;  
18.	    wire O_spi_cs;  
19.	    wire O_spi_mosi;  
20.	  
21.	    // Instantiate the Unit Under Test (UUT)  
22.	    spi_module uut (  
23.	        .I_clk       (I_clk         ),   
24.	        .I_rst_n     (I_rst_n    ),   
25.	        .I_rx_en      (I_rx_en    ),   
26.	        .I_tx_en      (I_tx_en    ),   
27.	        .I_data_in     (I_data_in   ),   
28.	        .O_data_out    (O_data_out  ),   
29.	        .O_tx_done    (O_tx_done    ),   
30.	        .O_rx_done    (O_rx_done    ),   
31.	        .I_spi_miso   (I_spi_miso   ),   
32.	        .O_spi_sck    (O_spi_sck    ),   
33.	        .O_spi_cs    (O_spi_cs    ),   
34.	        .O_spi_mosi   (O_spi_mosi   )  
35.	    );  
36.	  
37.	    initial begin  
38.	        // Initialize Inputs  
39.	        I_clk = 0;  
40.	        I_rst_n = 0;  
41.	        I_rx_en = 0;  
42.	        I_tx_en = 1;  
43.	        I_data_in = 8'h00;  
44.	        I_spi_miso = 0;  
45.	  
46.	        // Wait 100 ns for global reset to finish  
47.	        #100;  
48.	        I_rst_n = 1;    
49.	  
50.	    end  
51.	      
52.	    always #10 I_clk = ~I_clk ;  
53.	      
54.	    always @(posedge I_clk or negedge I_rst_n)  
55.	    begin  
56.	         if(!I_rst_n)  
57.	            I_data_in <= 8'h00;  
58.	         else if(I_data_in == 8'hff)  
59.	            begin  
60.	                I_data_in <= 8'hff;  
61.	                I_tx_en <= 0;  
62.	            end  
63.	         else if(O_tx_done)  
64.	            I_data_in <= I_data_in + 1'b1 ;              
65.	    end  
66.	        
67.	endmodule  

   ModelSim的仿真圖如下圖所示:
在這裏插入圖片描述
                      圖 2 31 ModelSim仿真波形
  由圖可以看到仿真得到的時序與SPI模式0的時序完全一致。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章