用VerilogHDL實現UART並完成仿真就算是對UART整個技術有了全面的理解,同時也算是Verilog入門了。整個UART分為3部分完成,發送模塊(Transmitter),接收模塊(Receiver)和波特率發生模塊(BuadRateGenerator)。發送模塊相比於接收模塊要簡單一些,主要功能就是每1/9600s發送1bit的數據,接收模塊就在采樣時鍾下完成數據的采樣,波特率發送模塊就是產生對應的波特率。UART的基本電路模型可以看UART學習之路(二) 基本時序介紹,當中對UART進行了完整的電路建模。
1.發送模塊
模塊代碼:
`timescale 1ns / 1ps module UART_TRANSMITTER( input [7:0] DataIn,//並行數據輸入
input baud16x,TxEn,rstn,//baud16x=波特率×16,TxEn是並行數據裝入使能信號,rstn復位信號
output reg DataOut,//串行數據輸出
output reg TxBusy// 說明串口正忙的信號,檢測其下降沿就可以判斷是否可以裝入新的數據
); reg [7:0] DataInReg;//輸入數據寄存器
reg [7:0] cnt;//計數器
reg cnt_start;//計數開始標志位
reg TransEnIn0;//當前狀態采樣
reg TransEnIn1;//上一個狀態采樣
wire pos_en; //采TxEn的上升沿
always@(posedge baud16x or negedge rstn) if(!rstn)begin TransEnIn0<=1'b0;
TransEnIn1<=1'b0;
end
else begin TransEnIn0 <= TxEn;//now
TransEnIn1 <= TransEnIn0;// delay
end
// Description // I0 I1 // 0 0 : 1&0=0 // 1 0 : 1&1=1 // 1 1 : 1&0=0 // 0 1 :0&0=0
assign pos_en = TransEnIn0 & !TransEnIn1; //數據裝入
always@(negedge rstn or posedge baud16x) if(!rstn)begin DataInReg <= 8'd0;
TxBusy <= 1'b0;
end
else if(pos_en == 1'b1)begin
DataInReg <= DataIn; cnt_start <= 1'b1;
TxBusy <= 1'b1;
end
else if(cnt >= 8'd160) begin
cnt_start <= 1'b0;
TxBusy <= 1'b0;
end
//計數
always@(posedge baud16x or negedge rstn) if(!rstn) cnt <= 8'd0;
else if(cnt_start == 1'b1)
cnt <= cnt + 1'b1;
else cnt <= 8'd0;
//UART 發送
always@(posedge baud16x or negedge rstn) if(!rstn) DataOut <=1'b1;
else if(cnt_start == 1'b1)
case(cnt) 8'd0:DataOut <= 1'b0; 8'd16:DataOut <= DataInReg[0];
8'd32:DataOut <= DataInReg[1];
8'd48:DataOut <= DataInReg[2];
8'd64:DataOut <= DataInReg[3];
8'd80:DataOut <= DataInReg[4];
8'd96:DataOut <= DataInReg[5];
8'd112:DataOut <= DataInReg[6];
8'd128:DataOut <= DataInReg[7];
8'd144:DataOut <= 1'b1; endcase
else DataOut <= 1'b1;
endmodule
TestBench
`timescale 1ns / 1ps module TESTBENCH_UART_TRANSMITTER( ); parameter CLKPERIOD = 100; reg [7:0] TempData; reg clk,en,nrst; wire TxDataOut; wire TxOverFlag; initial begin clk = 0; en = 0; nrst = 0; TempData = 8'd0;
end
//clk generate
always #(CLKPERIOD/2) clk = ~clk; //復位
initial begin #CLKPERIOD nrst = 1; end
//數據使能
initial begin #CLKPERIOD en = 1; TempData = 8'b1010_0101;
#CLKPERIOD en = 0; #(CLKPERIOD*200) en = 1; TempData = 8'b0101_1010;
#CLKPERIOD en = 0; end
//version 1.0 2018-12-4 //module initial
UART_TRANSMITTER U1(//input
.baud16x(clk), .TxEn(en), .rstn(nrst), .DataIn(TempData), //output
.DataOut(TxDataOut), .TxBusy(TxOverFlag)); endmodule
仿真結果
分析:
需要發送的並行數據存儲在TempData里面,第一次發送10100101,發送起始位bit0=0,之后是數據位bit1,bit2,bit3,bit4,bit5,bit6,bit7,bit8,最后是停止位bit9=1。第二次發送01011010,結果同第一次發送。第一張圖片,當en信號輸入由第變高,產生上升沿后,說明待發送的數據已經裝入發送寄存器了,發送端開始發送,首先發送的是LSB =1,最后發送的是MSB=1。第二張圖片中,同第一張圖片的過程,首先發送LSB=0,最后發送MSB=0。TxOverFlag表明發送端的忙狀態,高電平是忙,低電平是閑,通關檢測該信號的下降沿可以判斷發送是否完成。
2.接收模塊
模塊代碼
`timescale 1ns / 1ps /*2018-9-21 verson 1.0 baud rate = 9600 bit/s 1 bit takes 1/9600s = 104.167 us ~= 104167ns input frequency of clk is 100Mhz*/
// baud rate T_bdr N System clock = 50M // 9600 104167ns 104167/System_clk_priod 5208-1 // 19200 52083ns 52083/System_clk_priod 2604-1 // 38400 26041ns 26041/System_clk_priod 1302-1 // 57600 17361ns 17361/System_clk_priod 868-1 // 115200 8680ns 8680/System_clk_priod 434-1 //
//Input clock is 16x bound rate, the first sample data is at 24, and the next sample data is at 40 // 24,40,56,72,88,104,120,136,152
module UART_RECEIVE( input baud16x,rstn, input recv, output reg [7:0] rdata, output reg recv_ready ); reg recvIn0;//下降沿捕捉,當前時刻值
reg recvIn1;//下降沿捕捉,上一個時刻值
reg[7:0] cnt_bit; reg RecvNeFlag; //起始位獲取
always@(posedge baud16x or negedge rstn) if(!rstn) begin recvIn0 <= 1'b1;
recvIn1 <= 1'b1;
end
else begin
/*下降沿采樣*/ recvIn0 <= recv;//當前時刻的recv給recvIn0
recvIn1 <= recvIn0;//前一個時刻的recv給recvIn1
end
wire neg_rec; assign neg_rec = !recvIn0 && recvIn1; /*下降沿判斷,打開接收功能標志*/
always@(negedge rstn or posedge baud16x) if(!rstn) begin RecvNeFlag <= 1'b0;
recv_ready <= 1'b1;
end
else if(neg_rec == 1'b1)begin
RecvNeFlag <= 1'b1;
recv_ready <= 1'b0;
end
else if(cnt_bit == 8'd152)begin
RecvNeFlag <= 1'b0;
recv_ready <= 1'b1;
end
//bit計數
always@(posedge baud16x or negedge rstn) if(!rstn) cnt_bit <= 1'b0;
else if(RecvNeFlag == 1'b1)
cnt_bit <= cnt_bit + 1'b1;
else cnt_bit <= 8'd0;
//采樣接收
always@(posedge baud16x or negedge rstn) if(!rstn) rdata <= 8'b0000_0000;
else case(cnt_bit) 8'd24:rdata[0] <= recv;//數據位第1位
8'd40:rdata[1] <= recv;
8'd56:rdata[2] <= recv;
8'd72:rdata[3] <= recv;
8'd88:rdata[4] <= recv;
8'd104:rdata[5] <= recv;
8'd120:rdata[6] <= recv;
8'd136:rdata[7] <= recv;//數據位第8位
endcase
endmodule
2.TestBench
`timescale 1ns / 1ps module TESTBENCH_UART_RECEIVE( ); parameter CLKPERIOD=100; reg GCLK; reg nrst; reg DataIn; wire [7:0] recv_data; wire RecvDoneFlag; //初始化
initial begin nrst = 0; GCLK = 0; DataIn = 1; end
//生成時鍾激勵
always #(CLKPERIOD/2) GCLK = ~GCLK; //復位使能
initial #(CLKPERIOD*10) nrst = 1; //模擬發送數據
initial begin #(CLKPERIOD*49) DataIn = 0; #(CLKPERIOD*16) DataIn = 1; #(CLKPERIOD*16) DataIn = 0; #(CLKPERIOD*16) DataIn = 1; #(CLKPERIOD*16) DataIn = 0; #(CLKPERIOD*16) DataIn = 1; #(CLKPERIOD*16) DataIn = 0; #(CLKPERIOD*16) DataIn = 0; #(CLKPERIOD*16) DataIn = 1; #(CLKPERIOD*16) DataIn = 1; end UART_RECEIVE U1(//input
.baud16x(GCLK), .rstn(nrst), .recv(DataIn), //output
.rdata(recv_data), .recv_ready(RecvDoneFlag)); endmodule
3.仿真結果
分析:
模塊復位輸出8'b0000_0000,接收采樣在輸入的中點處。串行輸入數據,轉換成並行的數據。第一張圖發送的數據是8'b1111_1111,在接受端依次接收為8'b0000_0001,8'b0000_0011,…… ……,8'b1111_1111。接收到起始位后RecvDoneFlag信號由高拉低,表明接收端正忙,處於接收狀態,這個地方信號名字打錯了,后面懶得改了。接收完成后,RecvDoneFlag將由低拉高,判斷接收完成只需要檢測該信號的上升沿就行。第二張圖發送的數據是8'b1001_0101,過程如同第一張圖。觀察時序圖,發現recv_data數據變化的節點並不是8個,而是4個,這是因為模塊默認輸出的信號是8'b0000_0000,只有出現1的地方數據才會發生跳轉(出現采樣的痕跡),8’b1001_0101中有4個1,所以只有4個。
3.波特率發生模塊實際上就是對100Mhz的時鍾進行分頻,分成BaudRate*16的時鍾提供給發送和接收模塊。
4.參考代碼:https://github.com/jamieiles/uart ,GitHub上別人的另外一種實現方式。