用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上别人的另外一种实现方式。