本文為明德?lián)P原創(chuàng)及錄用文章,轉載請注明出處!
案例編號:000900000024
1.1 總體設計
1.1.1 概述
串行接口簡稱串口,也稱串行通訊接口或者串行通信接口,是采用串行通信方式的擴展接口。串行接口是指數(shù)據(jù)一位一位地順序傳送 ,其特點是通信線路簡單,只要一對傳輸線就可以實現(xiàn)雙向通信(可以直接利用電話線作為傳輸線),從而大大降低了成本,特別適合用于遠距離通信,但傳送速度較慢。一條信息的各位數(shù)據(jù)被逐位按順序傳送的通信方式稱作串行通信。串行通信的特點是:數(shù)據(jù)位的傳送,按位順序進行,最少只需要一根傳輸線即可完成;成本低,但傳送速度慢。串行通信的距離可以從幾米到幾千米;根據(jù)信息的傳送方向,串行通信可以進一步分為單工、半雙工和全雙工三種。
串口的出現(xiàn)是在 1980 年前后,數(shù)據(jù)傳輸率是 115kbps~230kbps。串口出現(xiàn)的初期是為了實現(xiàn)連接計算機外設的目的,初期串口一般用來連接鼠標和外置 Modem 以及老式攝像頭和寫字板等設備。
串口也可以應用于兩臺計算機(或設備)之間的互聯(lián)及數(shù)據(jù)傳輸。由于串口不支持熱插拔及傳輸速率較低,部分新主板和大部分便攜電腦已開始取消該接口。串口多用于工業(yè)控制和測量設備以及部分通信設備中。
1.1.2 設計目標
本練習要求實現(xiàn)串口回環(huán)功能,具體功能要求如下:
1、上位機于 FPGA 之間通過串口進行通信,規(guī)定波特率為 9600,數(shù)據(jù)位為 8bit,無奇偶校
驗位,停止位為 1。
2、 FPGA 內(nèi)部有一個可保存 128 字節(jié)的 FIFO。
3、 FPGA 從上位機接收到數(shù)據(jù)后,將數(shù)據(jù)保存到 FIFO 中。
4、當 FIFO 保存的數(shù)據(jù)超過 60 個數(shù)據(jù)時,啟動發(fā)送數(shù)據(jù)操作:讀取 FIFO 的數(shù)據(jù),將數(shù)據(jù)返
回給上位機。
5、 在啟動發(fā)送數(shù)據(jù)操作過程中,如果 FIFO 變空,結束發(fā)送操作,等待下一次的啟動。
注意:上位機接收到的數(shù)據(jù)與發(fā)送的數(shù)據(jù)相同,不能多也不能少。
1.1.3 系統(tǒng)結構框圖
系統(tǒng)結構框圖如下所示:
1.1.4 模塊功能
? 串口接收模塊實現(xiàn)功能
1、 將輸入數(shù)據(jù)進行同步化處理。
2、 解析串口時序,將有效數(shù)據(jù)進行串并轉換。
? 數(shù)據(jù)處理模塊實現(xiàn)功能
1、 包含一個 FIFO,用來存儲接收到的數(shù)據(jù)。
2、 滿足發(fā)送條件后,讀出 FIFO 的數(shù)據(jù)送給下游模塊。
? 串口發(fā)送模塊實現(xiàn)功能
1、 將接收到的數(shù)據(jù)進行并串轉換,發(fā)送給上位機。
1.1.5 頂層信號
1.1.6 參考代碼
下面是本工程的頂層代碼:
1. module com_prj(
2. rst_n ,
3. clk ,
4. rx_uart,
5. tx_uart
6. );
7. parameter BPS = 5208;
8.
9. input rst_n ;
10. input clk ;
11. input rx_uart ;
12. output tx_uart ;
13.
14. wire [7:0] uart_in ;
15. wire uart_in_vld ;
16. wire [7:0] uart_out ;
17. wire uart_out_vld;
18. wire rdy ;
19.
20. uart_rx#(.BPS(BPS)) uart_rx(
21. .clk (clk ),
22. .rst_n (rst_n ),
23. .din (rx_uart ),
24. .dout (uart_in ),
25. .dout_vld(uart_in_vld)
26. );
27. uart_tx#(.BPS(BPS)) uart_tx(
28. .clk (clk ),
29. .rst_n (rst_n ),
30. .din (uart_out ),
31. .din_vld (uart_out_vld),
32. .rdy (rdy ),
33. .dout (tx_uart )
34. );
35. data_handle u_data_handle(
36. .clk (clk ),
37. .rst_n (rst_n ),
38. .din (uart_in ),
39. .din_vld (uart_in_vld ),
40. .dout (uart_out ),
41. .dout_vld(uart_out_vld),
42. .rdy (rdy )
43. );
44.
45. endmodule
1.2 串口接收模塊設計
1.2.1 接口信號

1.2.2 設計思路
? UART 異步串行口簡介
數(shù)據(jù)通信的基本方式可分為并行通信和串行通信兩種:
并行通信:
是指利用多條數(shù)據(jù)線將一個資料的各位同時傳送。特點是傳輸速度快,適合用于短距
離通信,但要求通信速率較高的應用場合。
串行通信:
是指利用一條傳輸線將資料一位位的順序傳送。特點是通信線路簡單,利用簡單的線
纜就可以實現(xiàn)通信,減低成本,適用于遠距離通信,但傳輸速度慢的應用場合。
在 FPGA 看來,串口只有兩根線,一根線用于接收,一根線用于發(fā)送。

? UART 異步串行口的傳輸格式
異步通信以一個字符為傳輸單位,通信中兩個字符間的時間間隔是不固定的,然而在同一個字符
中的兩個相鄰位代碼間的時間間隔是固定的。
通信協(xié)議(通信規(guī)程):是指通信雙方約定的一些規(guī)則。在使用異步串行口傳送一個字符的信息
時,對資料格式有如下規(guī)定:規(guī)定有空閑位、起始位、數(shù)據(jù)位、奇偶校驗位、停止位。通訊時序圖如
下:

每一個數(shù)據(jù)位的寬度等于傳送波特率的倒數(shù)。微機異步串行通信中,常用的波特率為 110,150,
300,600,1200,2400,4800,9600 ,19200,38400,115200 等。代表每個碼元傳輸?shù)乃?/span>
率。在二進制數(shù)據(jù)傳輸中,波特率和比特率相同都是為每個比特數(shù)據(jù)傳輸?shù)乃俾?,其倒?shù)為 1bit
數(shù)據(jù)的寬度,也就是 1bit 數(shù)據(jù)持續(xù)的時間。確定這一時間,就可用 FPGA 構造計數(shù)器實現(xiàn)比特
周期的延時,從而實現(xiàn)特定波特率的數(shù)據(jù)傳輸。
? 開始前,線路處于空閑狀態(tài),送出連續(xù)"1"。傳送開始時首先發(fā)一個"0"作為起始位,
然后出現(xiàn)在通信線上的是字符的二進制編碼數(shù)據(jù)。
? 每個字符的數(shù)據(jù)位長可以約定為 5 位、6 位、7 位或 8 位。
? 后面是奇偶校驗位,顧名思義,檢驗位適用于數(shù)據(jù)校驗。分為奇校驗和偶校驗。奇校驗需
要保證傳輸數(shù)據(jù)總共有奇數(shù)個邏輯高電平,偶校驗則需要保證傳輸數(shù)據(jù)有偶數(shù)個邏輯高電
平。即"奇偶"指的是數(shù)據(jù)中(包括該校驗位)1 的個數(shù)。例如:傳輸?shù)臄?shù)據(jù)是 01000011,
如果校驗方式是奇校驗,則校驗位為 0 ,若是偶校驗則校驗位是 1.傳輸中校驗位不是必須
項,雙方可以約定不要校驗位,或者使用奇/偶校驗方式。
? 最后是表示停止位的"1"信號,這個停止位可以約定持續(xù) 1 位、1.5 位或 2 位的時間寬
度。由于每臺設備有其自己的時鐘,很可能再通信中兩臺設備間出現(xiàn)小小的不同步。因此
停止位不僅僅是表示傳輸?shù)慕Y束,并且提供一個校正時鐘同步的機會,讓從機可以正確的
識別下一輪數(shù)據(jù)的起始位。停止位的位數(shù)越多,不同時鐘同步的容忍程度就越大,但是數(shù)
據(jù)傳輸速率也越慢。
? 至此一個字符傳送完畢,線路又進入空閑,持續(xù)為"1"。經(jīng)過一段隨機的時間后,下一個
字符開始傳送才又發(fā)出起始位。
? 架構設計
上位機發(fā)送的數(shù)據(jù)會按照上圖所示串口的時序圖的順序過來,因此我們需要按照其對應的格式進
行接收。發(fā)送 8 位數(shù)據(jù) data 前,串口接收數(shù)據(jù)線會先變 0 并持續(xù)一段時間(起始位),然后發(fā)送 da
ta[0]、data[1],以此類推直至發(fā)送完 data[7],發(fā)送每位數(shù)據(jù)時都會持續(xù)一段時間,發(fā)送完畢后數(shù)據(jù)
線會變?yōu)?1 并持續(xù)一段時間(結束位)。至此,完成數(shù)據(jù)的發(fā)送。可以看出每段有效信號的開始前和
結束后,都會有特殊信號:有效數(shù)據(jù)開始前會有一段變 0 的信號,用以告知 FPGA 開始傳送數(shù)據(jù);
結束后會有一段變 1 的信號,告知 FPGA 此數(shù)據(jù)傳送結束。
由上面時序分析可知,當我們檢測到數(shù)據(jù)線從高電平(空閑位)變?yōu)榈碗娖剑ㄆ鹗嘉唬┚捅硎鹃_
始數(shù)據(jù)的傳輸了,因此需要進行下降沿的檢測,檢測方法如下:

該檢測方法主要利用 D 觸發(fā)器打拍來實現(xiàn)。
din:輸入串口數(shù)據(jù)。
din_ff0:輸入串口數(shù)據(jù)經(jīng)過一級緩存之后的信號,目的是為了將異步信號同步化。
din_ff1:輸入串口數(shù)據(jù)經(jīng)過二級緩存之后的信號,目的是為了減少亞穩(wěn)態(tài)造成的影響。
din_ff2:輸入串口數(shù)據(jù)經(jīng)過三級緩存之后的信號,目的是為了檢測信號的下降沿。
flag_add:接收狀態(tài)指示信號,當在時鐘上升沿檢測到 din_ff1 等于 0 并且 din_ff2 等于 1 的時候
表示檢測到了下降沿,此時將 flag_add 信號拉高,表示進入到接收狀態(tài)。
根據(jù)串口時序,可以提出兩個計數(shù)器的架構,如下圖所示:

該架構由兩個計數(shù)器組成:時鐘計數(shù)器 cnt 和數(shù)據(jù)計數(shù)器 data_num。
時鐘計數(shù)器 cnt:用于計數(shù)發(fā)送 1bit 數(shù)據(jù)所需要的時間,加一條件為 flag_add,表示進入接收狀
態(tài)時就開始計數(shù);結束條件為數(shù) 5208 個,開發(fā)板晶振時鐘是 50M,對應周期為 20ns,每位數(shù)據(jù)的
持續(xù)時間為 104166ns/20ns=5208.3 個時鐘周期,近似為 5208 個時鐘周期。
數(shù)據(jù)計數(shù)器 data_num:用于對接收的每一比特數(shù)據(jù)進行計數(shù),加一條件為 end_cnt,表示接收
到 1bit 的數(shù)據(jù)就加一;結束條件為數(shù) 9 個,一個起始位加上八個數(shù)據(jù)位,共 9 位,數(shù)完就清零。
? 注意事項
1、 串口接收模塊中的數(shù)據(jù)計數(shù)器一定不要把停止位也數(shù)上去,否則在接受的數(shù)據(jù)的時
候會出錯。感興趣的同學可以使用 signaltap 抓取信號進行分析(仿真沒有用)。
2、 由于工程中串口的每 1bit 數(shù)據(jù)傳輸所需要的時間是近似值,也就是存在誤差,因此
串口接收在采集數(shù)據(jù)的時候,需要在數(shù)據(jù)的中間時刻進行采樣,這樣才能保證數(shù)據(jù)
的正確性。
1.2.3 參考代碼
下面是使用明德?lián)P的計數(shù)器模板等寫出來的本模塊代碼。
1. always @ (posedge clk or negedge rst_n) begin
2. if(!rst_n) begin
3. din_ff0 <= 1'b1;
4. din_ff1 <= 1'b1;
5. din_ff2 <= 1'b1;
6. end
7. else begin
8. din_ff0 <= din;
9. din_ff1 <= din_ff0;
10. din_ff2 <= din_ff1;
11. end
12. end
13.
14. always @ (posedge clk or negedge rst_n)begin
15. if(!rst_n) begin
16. flag_add <= 1'b0;
17. end
18. else if(din_ff2 & ~din_ff1) begin
19. flag_add <= 1'b1;
20. end
21. else if(data_num==4'd8&&end_cnt) begin
22. flag_add <= 1'b0;
23. end
24. end
25.
26. always @ (posedge clk or negedge rst_n)begin
27. if(!rst_n)begin
28. cnt <= 0;
29. end
30. else if(add_cnt)begin
31. if(end_cnt)begin
32. cnt <= 0;
33. end
34. else begin
35. cnt <= cnt+1'b1;
36. end
37. end
38. else begin
39. cnt <= 0;
40. end
41. end
42. assign add_cnt = flag_add;
43. assign end_cnt = add_cnt && cnt == BPS-1;
44.
45.
46.
47. always @(posedge clk or negedge rst_n) begin
48. if (rst_n==0) begin
49. data_num <= 0;
50. end
51. else if(add_data_num) begin
52. if(end_data_num)
53. data_num <= 0;
54. else
55. data_num <= data_num+1 ;
56. end
57. end
58. assign add_data_num = end_cnt;
59. assign end_data_num = add_data_num && data_num == 9-1 ;
60.
61. always @ (posedge clk or negedge rst_n)begin
62. if(!rst_n) begin
63. dout <= 8'd0;
64. end
65. else if(add_cnt && cnt==BPS_P-1 && data_num!=0) begin
66. dout<={din,{dout[7:1]}};
67. end
68. else begin
69. dout<=dout;
70. end
71. end
72.
73. always @ (posedge clk or negedge rst_n)begin
74. if(!rst_n) begin
75. dout_vld <= 1'b0;
76. end
77. else if(add_data_num && data_num == 4'd8) begin
78. dout_vld <= 1'b1;
79. end
80. else begin
81. dout_vld <= 1'b0;
82. end
83. end
1.3 數(shù)據(jù)處理模塊設計
1.3.1 接口信號

1.3.2 設計思路
? FIFO 原理簡介
FIFO(first input first output),即先進先出的數(shù)據(jù)緩存器,本質上還是 RAM,與普通存儲器的區(qū)別:沒有外部讀寫地址線,這樣使用起來非常簡單。特點就是只能順序寫入數(shù)據(jù),順序讀出數(shù)據(jù),其數(shù)據(jù)地址由內(nèi)部讀寫指針自動加一完成,不能像普通存儲器那樣可以由地址線決定讀取或寫入某個指定的地址。
FIFO 根據(jù)讀寫時鐘的區(qū)別,分為同步 FIFO 和異步 FIFO。同步 FIFO 讀寫共用一個相同的時鐘;
異步 FIFO 讀寫可以使用不同的時鐘。由于異步 FIFO 內(nèi)部存在同步化電路,因此資源占用要比同步FIFO 大。
再 FPGA 中,F(xiàn)IFO 的使用主要有兩種場合,應用于緩存和跨時鐘域處理。FIFO 本身就是存儲器,自然可以用作數(shù)據(jù)的緩存,只不過在選擇上需要跟普通的 RAM 區(qū)分開。又由于異步 FIFO 的存
在,可以使得其寫側和讀側時鐘不同,因此又可用作跨時鐘域處理。
更多關于 FIFO 的內(nèi)容,可以關注明德?lián)P FIFO 專題課,或者是明德?lián)P免費的 FIFO 課程
? 架構設計
按照功能要求,本模塊需要對接收的數(shù)據(jù)進行存儲,當存夠 60 個的時候開始發(fā)送,下面是本模
塊的架構圖。

本模塊大致分為三部分:FIFO 的寫控制電路、FIFO、FIFO 的讀控制電路。
寫控制電路:只要輸入數(shù)據(jù)有效,就將數(shù)據(jù)寫進 FIFO,主要信號為寫數(shù)據(jù) data 和寫使能 wrreq。
FIFO ip 核:存儲數(shù)據(jù)。
讀控制電路:當 FIFO 內(nèi)部有 60 個數(shù)據(jù)、FIFO 非空并且下游模塊準備好接收數(shù)據(jù)的時候,就開
始讀。主要信號為輸出數(shù)據(jù) q、FIFO 有效數(shù)據(jù)量指示信號 usedw、空指示信號 empty、讀使能 rdreq。
1.3.3 參考代碼
84. assign data = din ;
85. assign wrreq = din_vld ;
86.
87.
88. my_fifo u_my_fifo (
89. .clock(clk ),
90. .data (data ),
91. .rdreq(rdreq),
92. .wrreq(wrreq),
93. .empty(empty),
94. .q (q ),
95. .usedw(usedw)
96. );
97.
98.
99. always@(*)begin
100. if(rd_flag && empty==1'b0 && rdy)
101. rdreq = 1'b1;
102. else
103. rdreq = 1'b0;
104. end
105.
106. always@(posedge clk or negedge rst_n)begin
107. if(rst_n==1'b0)begin
108. rd_flag <= 1'b0;
109. end
110. else if(rd_flag==1'b0 && usedw>=60) begin
111. rd_flag <= 1'b1;
112. end
113. else if(rd_flag==1'b1 && empty)begin
114. rd_flag <= 1'b0;
115. end
116. end
117.
118. always @(posedge clk or negedge rst_n)begin
119. if(rst_n==1'b0)begin
120. dout <= 0;
121. end
122. else begin
123. dout <= q;
124. end
125. end
126.
127. always @(posedge clk or negedge rst_n)begin
128. if(rst_n==1'b0)begin
129. dout_vld <= 1'b0;
130. end
131. else begin
132. dout_vld <= rdreq;
133. End
134. end
1.4 串口發(fā)送模塊設計
1.4.1 接口信號

1.4.2 設計思路
串口發(fā)送就是要按照串口的時序,對數(shù)據(jù)進行并串轉換,在介紹架構之前,先來描述一下本模塊
一些重要的信號的含義:
工作狀態(tài)指示信號 tx_flag:初始狀態(tài)為 0,表示處于空閑狀態(tài),當檢測到輸入數(shù)據(jù)有效的時候,
該信號變?yōu)?1,表示處于工作狀態(tài),當數(shù)據(jù)發(fā)送完之后,重新拉低,進入空閑狀態(tài),等待下一個數(shù)據(jù)的輸入。
數(shù)據(jù)鎖存信號 tx_data_tmp:位寬為 8bit,初始狀態(tài)為 0,當模塊處于空閑狀態(tài),并且輸入數(shù)據(jù)
有效的時候,就接收輸入的數(shù)據(jù)進行鎖存。由于輸入數(shù)據(jù)在串口發(fā)送的時間內(nèi)都需要用到,因此為了防止數(shù)據(jù)發(fā)生變化,導致串口發(fā)送出現(xiàn)問題,所以引入此信號進行數(shù)據(jù)的鎖存。
準備好接收指示信號 rdy:當接收到輸入數(shù)據(jù),或者模塊處于發(fā)送狀態(tài)的時候,此信號為 1,表
示不能接收數(shù)據(jù),其他情況為 1,表示準備好接收數(shù)據(jù)了。由于上游模塊數(shù)據(jù)輸出速率要比串口發(fā)送模塊發(fā)送的速度快得多,所以需要此信號來控制上游模塊的輸出,當串口發(fā)送模塊收到有效數(shù)據(jù)的時候,需要立刻把此信號拉高,所以需要用組合邏輯產(chǎn)生。
我們可以得到兩個計數(shù)器組成的計數(shù)器架構,如下圖所示:

該架構由兩個計數(shù)器組成:時鐘計數(shù)器 cnt 和數(shù)據(jù)計數(shù)器 data_num。
時鐘計數(shù)器 cnt:用于計數(shù)發(fā)送 1bit 數(shù)據(jù)所需要的時間,加一條件為 tx_flag,表示進入工作狀態(tài)
時就開始計數(shù);結束條件為數(shù) 5208 個,開發(fā)板晶振時鐘是 50M,對應周期為 20ns,每位數(shù)據(jù)的持
續(xù)時間為 104166ns/20ns=5208.3 個時鐘周期,近似為 5208 個時
溫馨提示:明德?lián)P2023推出了全新課程——邏輯設計基本功修煉課,降低學習FPGA門檻的同時,增加了學習的趣味性,并組織了考試贏積分活動
http://www.cqqtmy.cn/ffkc/415.html
(點擊→了解課程詳情?)感興趣請聯(lián)系易老師:13112063618(微信同步)