下面我將以模式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的時序完全一致。