用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,同第一次发送。
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,接收采样在输入的中点处。串行输入数据,转换成并行的数据。
3.波特率发生模块实际上就是对100Mhz的始终进行分频,分成BaudRate*16的时钟提供给发送和接收模块。
4.参考代码:https://github.com/jamieiles/uart ,GitHub上别人的另外一种实现方式。