小梅哥和你一起深入學習FPGA之串口調試(一)(上)
大家好,這幾天在各個論壇上,經常就有人在向我咨詢基于FPGA的串口通信代碼,大部分都是在網上下載一個現成的代碼,但是在使用中就遇到了各種問題,于是就發到了論壇上來求助。在閱讀了他們的代碼之后,我發現幾乎出自同一個版本(目前確定為特權同學的基于EPM240入門實驗的代碼)。他們在調試這個代碼的時候,經常存在這樣幾個問題:1、部分人對該串口通訊模塊完全不理解,對每句話,甚至每個模塊的功能都不理解;2、部分人采用最原始的畫波形的方式來對該模塊進行仿真,結果無法得到仿真結果;3、部分人不會使用modelsim對該設計進行仿真;4、絕大部分人不會編寫testbench;5、下板測試無法進行正確的字符串收發。在公司內部,我將這種現象和幾位老師交流之后,夏宇聞老師建議我專門針對該代碼寫一個由原理到代碼,由仿真到板級的調試筆記。爭取用最通俗,也是最笨的辦法,手把手的教會大家來調試這個代碼。
本文引用地址:http://www.j9360.com/article/283059.htm本調試筆記主要由五個部分組成:原始代碼分析;原始代碼驗證;對原始代碼進行修改;對修改后的代碼進行驗證;對修改后的設計進行板級驗證。每個部分,小梅哥都會用圖文結合的方式,教大家一步一步的來進行。
原始代碼分析
該代碼來自小梅哥最崇拜的大神,特權同學。當時小梅哥也是看著特權同學的書和視頻教程一步一步走過來的。特權同學的代碼實現了單字節的收發測試,沒有對連續字節的收發進行測試。特權同學當時也說過,這個只是一個簡單的實驗,離實際工業應用還有一定的距離。考慮到論壇上很多小伙伴都希望能夠實現連續字節的收發功能,因此小梅哥就在特權同學的代碼上進行了修改。修改后的代碼,輸入時鐘可以在一定范圍內選擇任意頻率,目前已經支持5種波特率選擇(9600、19200、38400、57600、115200),實際小梅哥還做過更高波特率的測試,目前實測在115200波特率的速率下可以實現超過9999999次連續無間斷的收發。這里,小梅哥首先將特權同學設計架構在這里列出來,以給讀者一個直觀的印象。

由上圖可知,特權同學的UART串口設計主要包含了四個模塊:串口發送模塊(my_uart_tx)、串口接收模塊(my_uart_rx)、串口接收波特率發生器(speed_rx)和串口發送波特率發生器(speed_tx),其中,串口發送波特率發生器主要用來產生串口發送模塊發送數據時所需的波特率時鐘,串口接收波特率發生器主要用來產生串口接收模塊接收數據時的波特率時鐘,串口發送模塊主要負責在指定波特率的速率下將待發送字節發送出去,串口接收模塊則主要負責接收來自其他設備發送過來的串口數據。my_uart_top模塊即串口收發頂層模塊實現了各個模塊間的信號連接功能,通過該頂層模塊的連接,實現了將串口接收到的數據再發送出去的功能,即我們測試串口通信最常用的一種方式——回環測試。特權同學該實驗的各個端口和內部信號的意義如表1所示:
該實驗的內容為,串口接收模塊在檢測到發送設備發送過來的數據起始位時,接收中斷信號置1,該信號則作為啟動串口接收波特率發送器的控制信號,然后在每個波特率時鐘上升沿到來時讀取串口接收端口(rs232_rx)上的數據。一幀(字節)數據接收完成后,接收中斷信號拉低,停止波特率發生器工作,接收完成,系統進入等待狀態,等待下一次的數據到來。
同時,接收中斷信號的下降沿也作為串口發送模塊的發送使能信號,因為一旦接收中斷信號的下降沿出現,就表明接收模塊完成了一次數據的接收,此時,就開始使能串口發送波特率發生器,串口發送模塊則在波特率時鐘的上升沿到來時依次將接收模塊接收到的數據的每一位(發送模塊自動添加起始位和停止位)依次發送出去,當數據發送完成之后,停止串口發送波特率發生器的使能,模塊進入等待狀態,等待下一次接收中斷信號下降沿的到來。
這里,我們首先對該設計的波特率發生器模塊進行分析。該模塊相對簡單,代碼如下所示:
以下是代碼片段:
1 module speed_select (
2 clk, rst_n ,
3 bps_start , clk_bps
4 );
5
6 input clk; // 50MHz
7 input rst_n ; //
8 input bps_start ; //
9 output clk_bps ; // clk_bps
10
11 /*
12 parameter bps9600 = 5207, // 9600bps
13 bps19200 = 2603, // 19200bps
14 bps38400 = 1301, // 38400bps
15 bps57600 = 867, // 57600bps
16 bps115200 = 433; // 115200bps
17
18 parameter bps9600_2 = 2603,
19 bps19200_2 = 1301,
20 bps38400_2 = 650,
21 bps57600_2 = 433,
22 bps115200_2 = 216;
23 */
24
25 //
26 `define BPS_PARA 5207 // 9600
27 `define BPS_PARA_2 2603 // 9600
28
29 reg[ 12 : 0] cnt ; //
30 reg clk_bps_r ; //
31
32 //---------------------------------------------------------
33 reg[ 2 : 0] uart_ctrl ; // uart
34 //---------------------------------------------------------
35
36 always @ ( posedge clk or negedge rst_n )
37 if(! rst_n ) cnt <= 13'd0 ;
38 else if(( cnt == `BPS_PARA ) || ! bps_start ) cnt <= 13'd0 ;
39 else cnt <= cnt+1'b1 ; //
40
41 always @ ( posedge clk or negedge rst_n )
42 if(! rst_n ) clk_bps_r <= 1'b0 ;
43 else if( cnt == `BPS_PARA_2 ) clk_bps_r <= 1'b1 ;
//clk_bps_r ,
44 else clk_bps_r <= 1'b0 ;
45
46 assign clk_bps = clk_bps_r ;
47
48 endmodule
該代碼的12-22行用注釋的方式告訴了我們,獲得不同波特率時波特率分頻計數器的值應該為多少,以及波特率計數器計數到一半時的值為多少(該值作為對信號的采樣點,因為一般情況下,一位數據,在該位數據保持時間的中間時刻是最穩定的)。26行和27行定義的兩個參數BPS_PARA和BPS_PARA_2分別就是波特率分頻計數器的最大值和中間值。實際使用時,只需要根據你所需要的波特率,更改這兩個參數的值即可 。例如,選擇波特率為9600bps時,設定BPS_PARA=5207,BPS_PARA_2=2603。關于這個值的計算,這里暫時不提,后文會有交代。
36行至39行為波特率分頻計數器的計數進程,即波特率發生模塊沒有被使能(! bps_start)或者計數器計數到BPS_PARA后則將計數器清零,其它情況下則每來一個時鐘上升沿計數器自加1。通過此進程,則可得到相對精準的波特率定時。
41行至44行為數據采樣時刻的生成,上面提到過“一般情況下,一位數據,在該位數據保持時間的中間時刻是最穩定的”,因此這里我們在計數器計數到一半時,產生一個時鐘周期的高脈沖,此脈沖作為采樣數據的使能信號。
評論