一、UART簡介
UART(universal asynchronous receiver-transmitter)是一種采用異步串行通信方式的通用異步收發傳輸器。一般來說,UART總是和RS232成對出現,那RS232又是什么呢? RS232也就是我們計算機上的串口,它的全稱是EIA-RS-232C (簡稱232,或者是RS232 )。其中EIA(Electronic Industry Association)代表美國電子工業協會,RS是Recommended Standard的縮寫,代表推薦標准,232 是標識符,C表示修改次數,它被廣泛用於計算機串行接口外設連接。如果你的計算機上還有串口的話,那么你就可以在主機箱后面看到RS232的接口:

隨着時代的發展,這種借口已經很少用了,取而代之的是“USB轉串口”,功能和原先一樣,但接口更高效了。
串口的主要功能為:在發送數據時將並行數據轉換成串行數據進行傳輸,在接收數據時將接收到的串行數據轉換成並行數據。這應該是大多數人接觸電子后學習到的第一個通信協議吧。
二、通信格式
下面來說說串口的具體要點:
1.傳輸時序
UART串口通信需要兩個信號線來實現,一根用於串口發送,另外一根負責串口接收。一開始高電平,然后拉低表示開始位,接着8個數據位,然后校驗位,最后拉高表示停止位,並且進入空閑狀態,等待下一次的數據傳輸。

很多時候我們的校驗位是允許省略的,所以協議就變成了:開始+數據+停止。

2.傳輸速率:波特率
串口通信的速率用波特率表示,它表示麥苗傳輸二進制數據的位數,單位是bps(位/秒)。常用的波特率有9600、19200、35400、57600以及115200等。
FPGA開發串口時,設計波特率的方法:FPGA的時鍾頻率/波特率。例如我的FPGA開發板時鍾頻率為50Mhz,即50_000_000hz,我想使用的波特率為9600bps,因此我需要的計數為:50000000/9600≈5208。
三、串口回環設計
現在用FPGA開發板做一個串口回環的實驗,要求是PC端通過串口助手發送數據給FPGA,FPGA接收到數據后返回給PC端,並在串口助手處顯示數值。即串口助手發什么就能收回什么。實驗框圖如下:

1.uart_rx
1 //************************************************************************** 2 // *** 名稱 : uart_rx.v 3 // *** 作者 : xianyu_FPGA 4 // *** 博客 : https://www.cnblogs.com/xianyufpga/ 5 // *** 日期 : 2019-01-10 6 // *** 描述 : 串口接收模塊,計數9.5下,其中停止位0.5下 7 // 因為串口助手發送本次停止位和下次開始位中間沒有留空閑位 8 // 若計滿10下,則才結束本次傳輸下次數據就來了,會來不及接收 9 //************************************************************************** 10 11 module uart_rx 12 //========================< 參數 >========================================== 13 #( 14 parameter CLK = 50_000_000 , //系統時鍾,50Mhz 15 parameter BPS = 9600 , //波特率 16 parameter BPS_CNT = CLK/BPS //波特率計數 17 ) 18 //========================< 端口 >========================================== 19 ( 20 input wire clk , //時鍾,50Mhz 21 input wire rst_n , //復位,低電平有效 22 input wire din , //輸入數據 23 output reg [7:0] dout , //輸出數據 24 output reg dout_vld //輸出數據的有效指示 25 ); 26 //========================< 信號 >========================================== 27 reg rx0 ; 28 reg rx1 ; 29 reg rx2 ; 30 wire rx_en ; 31 reg flag ; 32 reg [15:0] cnt0 ; 33 wire add_cnt0 ; 34 wire end_cnt0 ; 35 reg [ 3:0] cnt1 ; 36 wire add_cnt1 ; 37 wire end_cnt1 ; 38 reg [ 7:0] data ; 39 40 //========================================================================== 41 //== 消除亞穩態 + 下降沿檢測 42 //========================================================================== 43 always @(posedge clk or negedge rst_n) begin 44 if(!rst_n) begin 45 rx0 <= 1; 46 rx1 <= 1; 47 rx2 <= 1; 48 end 49 else begin 50 rx0 <= din; 51 rx1 <= rx0; 52 rx2 <= rx1; 53 end 54 end 55 56 assign rx_en = rx2 && ~rx1; 57 58 //========================================================================== 59 //== 接收狀態指示 60 //========================================================================== 61 always @(posedge clk or negedge rst_n) begin 62 if(!rst_n) 63 flag <= 0; 64 else if(rx_en) 65 flag <= 1; 66 else if(end_cnt1) 67 flag <= 0; 68 end 69 70 //========================================================================== 71 //== 波特率計數 72 //========================================================================== 73 always @(posedge clk or negedge rst_n) begin 74 if(!rst_n) 75 cnt0 <= 0; 76 else if(add_cnt0) begin 77 if(end_cnt0) 78 cnt0 <= 0; 79 else 80 cnt0 <= cnt0 + 1; 81 end 82 end 83 84 assign add_cnt0 = flag; 85 assign end_cnt0 = cnt0== BPS_CNT-1 || end_cnt1; 86 87 //========================================================================== 88 //== 開始1位(不接收) + 數據8位 + 停止0.5位(不接收),共10位 89 //========================================================================== 90 always @(posedge clk or negedge rst_n) begin 91 if(!rst_n) 92 cnt1 <= 0; 93 else if(add_cnt1) begin 94 if(end_cnt1) 95 cnt1 <= 0; 96 else 97 cnt1 <= cnt1 + 1; 98 end 99 end 100 101 assign add_cnt1 = end_cnt0; 102 assign end_cnt1 = cnt1==10-1 && cnt0==BPS_CNT/2-1; 103 104 //========================================================================== 105 //== 緩存數據 106 //========================================================================== 107 always @ (posedge clk or negedge rst_n)begin 108 if(!rst_n) 109 data <= 8'd0; 110 else if(cnt1>=1 && cnt1<=8 && cnt0==BPS_CNT/2-1) //中間采樣 111 data[cnt1-1] <= rx2; //或 dout <= {rx2,dout[7:1]}; 112 end 113 114 //========================================================================== 115 //== 輸出數據 116 //========================================================================== 117 always @ (posedge clk or negedge rst_n)begin 118 if(!rst_n) 119 dout <= 0; 120 else if(end_cnt1) 121 dout <= data; 122 end 123 124 always @ (posedge clk or negedge rst_n)begin 125 if(!rst_n) 126 dout_vld <= 0; 127 else if(end_cnt1) 128 dout_vld <= 1; 129 else 130 dout_vld <= 0; 131 end 132 133 134 135 endmodule
2.uart_tx
1 //************************************************************************** 2 // *** 名稱 : uart_tx.v 3 // *** 作者 : xianyu_FPGA 4 // *** 博客 : https://www.cnblogs.com/xianyufpga/ 5 // *** 日期 : 2019-01-10 6 // *** 描述 : 串口接收模塊,計數9.5下,其中停止位0.5下 7 // 因為極端情況是本次停止位和下次開始位中間沒有留空閑位 8 // 若計滿10下,則才結束本次傳輸下次數據就來了,會來不及發送 9 //************************************************************************** 10 11 module uart_tx 12 //========================< 參數 >========================================== 13 #( 14 parameter CLK = 50_000_000 , //系統時鍾,50Mhz 15 parameter BPS = 9600 , //波特率 16 parameter BPS_CNT = CLK/BPS //波特率計數 17 ) 18 //========================< 端口 >========================================== 19 ( 20 input wire clk , //時鍾,50Mhz 21 input wire rst_n , //復位,低電平有效 22 input wire [7:0] din , //輸入數據 23 input wire din_vld , //輸入數據的有效指示 24 output reg dout //輸出數據 25 ); 26 //========================< 信號 >========================================== 27 reg flag ; 28 reg [ 7:0] din_tmp ; 29 reg [15:0] cnt0 ; 30 wire add_cnt0 ; 31 wire end_cnt0 ; 32 reg [ 3:0] cnt1 ; 33 wire add_cnt1 ; 34 wire end_cnt1 ; 35 wire [ 9:0] data ; 36 37 //========================================================================== 38 //== 數據暫存(din可能會消失,暫存住) 39 //========================================================================== 40 always @ (posedge clk or negedge rst_n) begin 41 if(!rst_n) 42 din_tmp <=8'd0; 43 else if(din_vld) 44 din_tmp <= din; 45 end 46 47 //========================================================================== 48 //== 發送狀態指示 49 //========================================================================== 50 always @(posedge clk or negedge rst_n)begin 51 if(!rst_n) 52 flag <= 0; 53 else if(din_vld) 54 flag <= 1; 55 else if(end_cnt1) 56 flag <= 0; 57 end 58 59 //========================================================================== 60 //== 波特率計數 61 //========================================================================== 62 always @(posedge clk or negedge rst_n) begin 63 if(!rst_n) 64 cnt0 <= 0; 65 else if(add_cnt0) begin 66 if(end_cnt0) 67 cnt0 <= 0; 68 else 69 cnt0 <= cnt0 + 1; 70 end 71 end 72 73 assign add_cnt0 = flag; 74 assign end_cnt0 = cnt0== BPS_CNT-1 || end_cnt1; 75 76 //========================================================================== 77 //== 開始1位 + 數據8位 + 停止0.5位,共10位 78 //========================================================================== 79 always @(posedge clk or negedge rst_n) begin 80 if(!rst_n) 81 cnt1 <= 0; 82 else if(add_cnt1) begin 83 if(end_cnt1) 84 cnt1 <= 0; 85 else 86 cnt1 <= cnt1 + 1; 87 end 88 end 89 90 assign add_cnt1 = end_cnt0; 91 assign end_cnt1 = cnt1==10-1 && cnt0==BPS_CNT/2-1; 92 93 //========================================================================== 94 //== 數據輸出(用case語句也行) 95 //========================================================================== 96 assign data = {1'b1,din_tmp,1'b0}; //停止,數據,開始 97 98 always @(posedge clk or negedge rst_n) begin 99 if(!rst_n) 100 dout <= 1'b1; 101 else if(flag) 102 dout <= data[cnt1]; 103 end 104 105 106 107 endmodule
3.top層
1 //************************************************************************** 2 // *** 名稱 : uart_top.v 3 // *** 作者 : xianyu_FPGA 4 // *** 博客 : https://www.cnblogs.com/xianyufpga/ 5 // *** 日期 : 2019-01-10 6 // *** 描述 : 串口實驗頂層文件 7 //************************************************************************** 8 9 module uart_top 10 //========================< 端口 >========================================== 11 ( 12 input wire clk , //時鍾,50Mhz 13 input wire rst_n , //復位,低電平有效 14 input wire uart_rx , //FPGA通過串口接收的數據 15 output wire uart_tx //FPGA通過串口發送的數據 16 ); 17 18 //========================< 連線 >========================================== 19 wire [7:0] data ; 20 wire data_vld ; 21 22 //========================================================================== 23 //== 模塊例化 24 //========================================================================== 25 uart_rx 26 #( 27 .BPS_CNT (52 ) //仿真用 28 ) 29 u_uart_rx 30 ( 31 .clk (clk ), 32 .rst_n (rst_n ), 33 .din (uart_rx ), 34 .dout (data ), 35 .dout_vld (data_vld ) 36 ); 37 38 uart_tx 39 #( 40 .BPS_CNT (52 ) //仿真用 41 ) 42 u_uart_tx 43 ( 44 .clk (clk ), 45 .rst_n (rst_n ), 46 .din_vld (data_vld ), 47 .din (data ), 48 .dout (uart_tx ) 49 ); 50 51 52 53 endmodule
四、仿真調試
1、testbench
1 `timescale 1ns/1ps //時間精度 2 `define Clock 20 //時鍾周期 3 4 module uart_top_tb; 5 6 //========================< 端口 >========================================== 7 reg clk ; //時鍾,50Mhz 8 reg rst_n ; //復位,低電平有效 9 reg uart_rx ; 10 wire uart_tx ; 11 12 //========================================================================== 13 //== 模塊例化 14 //========================================================================== 15 uart_top u_uart_top 16 ( 17 .clk (clk ), 18 .rst_n (rst_n ), 19 .uart_rx (uart_rx ), 20 .uart_tx (uart_tx ) 21 ); 22 23 //========================================================================== 24 //== 時鍾信號和復位信號 25 //========================================================================== 26 initial begin 27 clk = 1; 28 forever 29 #(`Clock/2) clk = ~clk; 30 end 31 32 initial begin 33 rst_n = 0; #(`Clock*20+1); 34 rst_n = 1; 35 end 36 37 //========================================================================== 38 //== task任務 39 //========================================================================== 40 reg [7:0] mem[15:0] ; //位寬為8,深度為16個數據 41 integer i ; 42 integer j ; 43 44 //讀取外部數據 45 initial $readmemh("./data.txt",mem); 46 47 //位賦值 48 task rx_bit 49 ( 50 input [7:0] data 51 ); 52 begin 53 for(i=0;i<=9;i=i+1) begin //10個bit為 54 case(i) 55 0: uart_rx = 1'b0; 56 1: uart_rx = data[i-1]; 57 2: uart_rx = data[i-1]; 58 3: uart_rx = data[i-1]; 59 4: uart_rx = data[i-1]; 60 5: uart_rx = data[i-1]; 61 6: uart_rx = data[i-1]; 62 7: uart_rx = data[i-1]; 63 8: uart_rx = data[i-1]; 64 9: uart_rx = 1'b1; 65 endcase 66 #1040; //一個完整波特延時:52*20=1040 67 end //考慮到空閑位,也可以設置得1040稍大一些 68 end 69 endtask 70 71 //字節賦值 72 task rx_byte; 73 begin 74 for(j=0;j<=15;j=j+1) //16個byte數據 75 rx_bit(mem[j]); 76 end 77 endtask 78 79 //========================================================================== 80 //== 調用task 81 //========================================================================== 82 initial begin 83 #(`Clock*20+1); 84 rx_byte(); 85 end 86 87 initial begin 88 #180000; 89 $stop; 90 end 91 92 endmodule
2、data.txt
testbench中調用了一個 data.txt 文本文檔,里面存儲了此次仿真的16個數據,將其放置到 Modelsim 軟件的工程目錄中(非 work)即可。
0 1 2 3 4 5 6 7 8 9 a b c d e f
3、仿真波形
由波形可以看到,本次設計應該是成功的。

五、上板驗證
本次上位機采用友善串口助手,無校驗位,停止位為1。當串口助手發送數據給FPGA后,FPGA很快又將原數據返回給上位機。

經上板驗證,本次設計成功!
參考資料:
[1]明德揚FPGA教程
[2]正點原子FPGA教程
[2]威三學院FPGA教程
