UART Verilog


先说uart的发送模块的要求吧,驱动时钟是40kHz的 clk_40k(要求波特率为1k,所以要40分频),复位信号是低电平复位的 rst_n,输入数据是8位并行的 din,发送使能信号是 send_start,输出信号是 bit_out

  • 该模块接收一个8位输入数据并采用UART协议发送
  • UART 采用10位传输协议,即一位起始位,8位数据位,一位终止位
  • 发送数据时先发低位数据,最后发高位数据。要求波特率为1K
  • 发送第一个数据前发送一个0作为起始标志,发送完最后一个数据后发送1作为结束标志,直到下一个数据周期

让我们先回忆下uart的工作原理,uart是一种异步传输方式。

  • 发端比较简单,每当send_start=1代表发送开始,先发一个0,再发所有数据,最后发个1等待下一个send_start
  • 收端相对就会比较麻烦了。因为是异步信号,谁也没法保证时钟对齐。要是比较倒霉,还可能会遇到亚阈值效应。我们先暂且不考虑这些,先考虑如何检测开始信号。考虑到空闲状态是1,起始先要发一个0,这就意味着会有一个下降沿。我们只要先检测下降沿即可,检测到就代表发送开始。然后采样时可以多采几个样本,采样点尽可能靠近中间(因为这时一般比较稳定),然后择多译码。

发端代码

先让我们写比较简单的发端代码吧。

首先是send_start的检测吧,因为send_start只会持续一个时钟周期长度,这意味着我们必须用40kHz的信号always模块进行检测,不能乱分频。具体代码如下,我们可以看到,我们用了一个寄存器储存了send_start信号,每当它为1时,这个寄存器置1,直到400个周期((clk_cnt == 6'd39) && (bit_cnt == 4'd9))过去后我们再将其清零。

reg                 send_en;        // 发送时,send_en为1

// send_en信号
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        send_en <= 1'b0;
    else if (send_start)
        send_en <= 1'b1;
    else if ((clk_cnt == 6'd39) && (bit_cnt == 4'd9))
        send_en <= 1'b0;
    else
        send_en <= send_en;
end

然后是40分频,这个很简单。

reg[5:0]            clk_cnt;        // 分频

// clk_cnt 分频信号
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        clk_cnt <= 6'd0;
    else if (send_en) begin
        if (clk_cnt == 6'd39)
            clk_cnt <= 6'd0;
        else
            clk_cnt <= clk_cnt + 6'd1;
    end
    else
        clk_cnt <= 6'd0;
end

然后对当前发送bit数计数。bit_cnt代表目前发送到第几个bit了。(clk_cnt == 6'd39) && (bit_cnt==4'd9)则是代表发完了,此时归0

reg[3:0]            bit_cnt;        // 记录发到了第几bit

// bit_cnt 分频信号
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        bit_cnt <= 4'd0;
    else if (send_en) begin
        if ((clk_cnt == 6'd39) && (bit_cnt==4'd9))  // 防止bit_cnt到10
            bit_cnt <= 4'd0;
        else if (clk_cnt == 6'd39)                  // 每40周期+1
            bit_cnt <= bit_cnt + 4'd1;
        else
            bit_cnt <= bit_cnt;
    end
    else
        bit_cnt <= 4'd0;
end

最后就是输出部分了。最开始总是先发0,然后发din,最后发1。注意尽量不要让bit_cnt>=10,不然可能出bug。

reg[9:0]            din_tmp;        // 缓存din
assign bit_out = (send_en ? din_tmp[bit_cnt] : 1'b1);

// din_tmp
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        din_tmp <= 10'b11_1111_1110;
    else if (send_start)
        din_tmp <= {1'b1, din, 1'b0};
    else
        din_tmp <= din_tmp;
end

uart_tx.v

把以上代码综合起来就是发端的代码了。

`timescale  1ns / 1ns  

module uart_tx (    clk_40k,        // clock signal, 40kHz
                    rst_n,          // reset signal, active low
                    din,            // the input data which will be sent by the UART module, 8 bit width
                    send_start,     // the start enable signal, active high, the width is one clock period
                    bit_out	        // the serial output data 
                    );

input   wire        clk_40k;        // 40kHz的时钟
input   wire        rst_n;          // 低电平复位
input   wire[7:0]   din;            // 8位输入数据
input   wire        send_start;     // 高电平代表发数据
output  wire        bit_out;        // 输出

reg                 send_en;        // 发送时,send_en为1
reg[5:0]            clk_cnt;        // 分频
reg[3:0]            bit_cnt;        // 记录发到了第几bit
reg[9:0]            din_tmp;        // 缓存din

// send_en信号
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        send_en <= 1'b0;
    else if (send_start)
        send_en <= 1'b1;
    else if ((clk_cnt == 6'd39) && (bit_cnt == 4'd9))
        send_en <= 1'b0;
    else
        send_en <= send_en;
end

// clk_cnt 分频信号
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        clk_cnt <= 6'd0;
    else if (send_en) begin
        if (clk_cnt == 6'd39)
            clk_cnt <= 6'd0;
        else
            clk_cnt <= clk_cnt + 6'd1;
    end
    else
        clk_cnt <= 6'd0;
end

// bit_cnt 分频信号
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        bit_cnt <= 4'd0;
    else if (send_en) begin
        if ((clk_cnt == 6'd39) && (bit_cnt==4'd9))  // 防止bit_cnt到10
            bit_cnt <= 4'd0;
        else if (clk_cnt == 6'd39)                  // 每40周期+1
            bit_cnt <= bit_cnt + 4'd1;
        else
            bit_cnt <= bit_cnt;
    end
    else
        bit_cnt <= 4'd0;
end

assign bit_out = (send_en ? din_tmp[bit_cnt] : 1'b1);

// din_tmp
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        din_tmp <= 10'b11_1111_1110;
    else if (send_start)
        din_tmp <= {1'b1, din, 1'b0};
    else
        din_tmp <= din_tmp;
end

endmodule

tb_uart_tx

然后来一个发端的testbench

//~ `New testbench
`timescale  1ns / 1ns  

module tb_uart_tx;     

// uart_tx Parameters  
parameter PERIOD  = 10;


// uart_tx Inputs
reg         clk_40k                              = 1 ;
reg         rst_n                                = 1 ;
reg[7:0]    din                                  = 8'b00101110 ;
reg         send_start                           = 0 ;

// uart_tx Outputs
wire  bit_out                              ;


initial
begin
    forever #(PERIOD/2)  clk_40k=~clk_40k;
end

initial
begin
    #(PERIOD*2) rst_n  =  1;
end

uart_tx  u_uart_tx (
    .clk_40k                 ( clk_40k            ),
    .rst_n                   ( rst_n              ),
    .din                     ( din                ),
    .send_start              ( send_start         ),

    .bit_out                 ( bit_out            )
);

initial
begin
    #(PERIOD*2)      rst_n=0;
    #(PERIOD*40)     rst_n=1;
    #(PERIOD*40)     send_start=1;
    #(PERIOD*1)      send_start=0;
    #(PERIOD*600);
    $finish;
end

endmodule

发端仿真图

在这里插入图片描述

在这里插入图片描述

收端代码

再让我们写比较难的收端代码吧。

首先我们可以先考虑输出这块,因为可能有亚阈值效应和抖动等,我们通过D触发器的级联消除亚阈值效应。一般来说2个到3个D触发器的级联的效果就很不错了。我们定义5个D触发器的级联为bit_in_dly,输入从低位进入这个移位寄存器,将最高位bit_in_dly[4]作为我们实际使用的bit_in。这样可以增强我们接受的可靠性。

reg[4:0]            bit_in_dly;

// 输出bit
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        bit_in_dly <= 5'b11111;
    else
        bit_in_dly <= {bit_in_dly[3:0], bit_in};
end

然后写bit的采样,为了增强可靠性,我们可以采3次样,择多译码。具体思路是在16时将计数器清零,然后分别在18/20/22处采样。我们假设3次都采样到0,sum_tmp=2'b00,我们假设3次都采样到1,sum_tmp=2'b11。假设有1次误码,本来应该3个0变成了2个0、1个1,sum_tmp=2'b01;或者本来应该3个1变成了2个1、1个0,sum_tmp=2'b10

分析所有情况,其实本质上就是取sum_tmp的最高位sum_tmp[1]就行了。至于为什么在18/20/22处采样,这是因为我们假设每个bit的中间是比较稳定的。再加上3次采样择多译码,错误概率就很低了。

reg[1:0]            sum_tmp;

// sum_tmp
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        sum_tmp <= 2'd0;
    else if (clk_cnt == 6'd16)
        sum_tmp <= 2'd0;
    else if ((clk_cnt == 6'd18) || (clk_cnt == 6'd20) || (clk_cnt == 6'd22))
        sum_tmp <= sum_tmp + bit_in_dly[4];
    else
        sum_tmp <= sum_tmp;
end

我们还需要标记bit接收的开始信号,本质上就是检测下降沿。下降沿其实就是1100这种信号,不过我们在这里偷一个懒,我们只需要检测到0就可以认为是下降沿。

这是为什么呢?因为我们开始一定会发一个0,结束一定是1,要是还不发比特,处于空闲状态依然是1。这意味着我们只需要发现0,这一定意味这不处于空闲状态,并且第一个bit必然是0。我们只需要检测到0后给计数器置1,持续9个多bit的时间((bit_cnt == 4'd9) && (clk_cnt == 6'd30)相当于9.75个bit),这样就能让结束时必然处于1状态,此时再出现0,必然是一个下降沿。

reg                 rcv_en;
assign nedge = (bit_in_dly[4:2] == 3'b0);

// rcv_en
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        rcv_en <= 1'b0;
    else if (nedge)
        rcv_en <= 1'b1;
    else if ((bit_cnt == 4'd9) && (clk_cnt == 6'd30))
        rcv_en <= 1'b0;
    else
        rcv_en <= rcv_en;
end

clk_cntbit_cnt和发端是一致的

reg[5:0]            clk_cnt;        // 分频
reg[3:0]            bit_cnt;        // 记录发到了第几bit

// clk_cnt 分频信号
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        clk_cnt <= 6'd0;
    else if (rcv_en) begin
        if (clk_cnt == 6'd39)
            clk_cnt <= 6'd0;
        else
            clk_cnt <= clk_cnt + 6'd1;
    end
    else
        clk_cnt <= 6'd0;
end

// bit_cnt 分频信号
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        bit_cnt <= 4'd0;
    else if (rcv_en) begin
        if ((clk_cnt == 6'd39) && (bit_cnt==4'd9))  // 防止bit_cnt到10
            bit_cnt <= 4'd0;
        else if (clk_cnt == 6'd39)                  // 每40周期+1
            bit_cnt <= bit_cnt + 4'd1;
        else
            bit_cnt <= bit_cnt;
    end
    else
        bit_cnt <= 4'd0;
end

然后接收信息bit

output  reg[7:0]    dout;           // 输出

// dout
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        dout <= 8'd255;
    else if ((clk_cnt == 6'd24) && (bit_cnt >= 4'd1) && (bit_cnt <= 4'd8))
        dout[bit_cnt-1] <= sum_tmp[1];
    else
        dout <= dout;
end

uart_rx.v

合到一起就是uart_rx.v了。

`timescale  1ns / 1ns  

module uart_rx (    clk_40k,        // clock signal, 40kHz
                    rst_n,          // reset signal, active low
                    bit_in,         // the input serial bit,
                    dout_vld,       // the output valid signal, active high,the dout is valid when this signal is high.
                    dout	        // received data, 8 bit width 
                    );

input   wire        clk_40k;        // 40kHz的时钟
input   wire        rst_n;          // 低电平复位
input   wire        bit_in;         // 串行输入数据
output  wire        dout_vld;       // 高电平代表收数据
output  reg[7:0]    dout;           // 输出

reg[4:0]            bit_in_dly;
reg[5:0]            clk_cnt;        // 分频
reg[3:0]            bit_cnt;        // 记录发到了第几bit
reg[1:0]            sum_tmp;
reg                 rcv_en;

assign dout_vld = (bit_cnt == 4'd9);

// 输出bit
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        bit_in_dly <= 5'b11111;
    else
        bit_in_dly <= {bit_in_dly[3:0], bit_in};
end

// sum_tmp
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        sum_tmp <= 2'd0;
    else if (clk_cnt == 6'd16)
        sum_tmp <= 2'd0;
    else if ((clk_cnt == 6'd18) || (clk_cnt == 6'd20) || (clk_cnt == 6'd22))
        sum_tmp <= sum_tmp + bit_in_dly[4];
    else
        sum_tmp <= sum_tmp;
end

// dout
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        dout <= 8'd255;
    else if ((clk_cnt == 6'd24) && (bit_cnt >= 4'd1) && (bit_cnt <= 4'd8))
        dout[bit_cnt-1] <= sum_tmp[1];
    else
        dout <= dout;
end

assign nedge = (bit_in_dly[4:2] == 3'b0);

// rcv_en
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        rcv_en <= 1'b0;
    else if (nedge)
        rcv_en <= 1'b1;
    else if ((bit_cnt == 4'd9) && (clk_cnt == 6'd30))
        rcv_en <= 1'b0;
    else
        rcv_en <= rcv_en;
end

// clk_cnt 分频信号
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        clk_cnt <= 6'd0;
    else if (rcv_en) begin
        if (clk_cnt == 6'd39)
            clk_cnt <= 6'd0;
        else
            clk_cnt <= clk_cnt + 6'd1;
    end
    else
        clk_cnt <= 6'd0;
end

// bit_cnt 分频信号
always @( posedge clk_40k or negedge rst_n ) begin
    if (~rst_n)
        bit_cnt <= 4'd0;
    else if (rcv_en) begin
        if ((clk_cnt == 6'd39) && (bit_cnt==4'd9))  // 防止bit_cnt到10
            bit_cnt <= 4'd0;
        else if (clk_cnt == 6'd39)                  // 每40周期+1
            bit_cnt <= bit_cnt + 4'd1;
        else
            bit_cnt <= bit_cnt;
    end
    else
        bit_cnt <= 4'd0;
end

endmodule

tb_uart.v

整个的testbench如下

//~ `New testbench
`timescale  1ns / 1ns  

module tb_uart;     

// uart Parameters  
parameter PERIOD  = 10;


// uart Inputs
reg         clk_40k                              = 1 ;
reg         clk_40k_rx                           = 0 ;
reg         rst_n                                = 1 ;
reg[7:0]    din                                  = 8'b00101110 ;
reg         send_start                           = 0 ;

// uart Outputs
wire  bit_out                              ;
wire  bit_in                               ;
wire  dout_vld                             ;
wire  dout                                 ;


initial
begin
    forever #(PERIOD/2)  clk_40k=~clk_40k;
end
initial
begin
    forever #(PERIOD/2)  clk_40k_rx=~clk_40k_rx;
end

uart_tx  u_uart_tx (
    .clk_40k                 ( clk_40k            ),
    .rst_n                   ( rst_n              ),
    .din                     ( din                ),
    .send_start              ( send_start         ),
    .bit_out                 ( bit_out            )
);

uart_rx  u_uart_rx (
    .clk_40k                 ( clk_40k_rx         ),
    .rst_n                   ( rst_n              ),
    .bit_in                  ( bit_out            ), // bit_in = bit_out
    .dout_vld                ( dout_vld           ),
    .dout                    ( dout               )
);

initial
begin
    #(PERIOD*2)      rst_n=0;
    #(PERIOD*40)     rst_n=1;
    #(PERIOD*40)     send_start=1;
    #(PERIOD*1)      send_start=0;
    #(PERIOD*600);
    $finish;
end

endmodule
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值