基于FPGA的UART接收模快(全模式设置)

基于FPGA的UART接收模快(全模式设置)

  上次版本的缺陷就是没有解决奇偶校验,本次设计主要解决该问题,并且加入停止位位数的选择。

1、校验位及停止位原理

1.1、奇偶校验

  接收方和发送方会约定一种校验方式,在一定程度内,来检测接收到的数据是否正确。通常专门设置一个奇偶校验位,用它使这组数据和奇偶校验位中"1"的个数为奇数或偶数。

  奇校验:让传输数据(包含校验位)中1的个数为奇数。即:如果数据位中1的个数是偶数,则校验位为“1”,个数为奇数则校验位为”0”。数据位和校验位发送给接受方后,接收方再次对数据位和校验位中1的个数进行统计,如果为奇数则校验通过,表示此次传输过程未发生错误。如果不是奇数,则表示有错误发生,此时接收方可以向发送方发送请求,要求重新发送一遍数据。

  偶校验:让传输数据(包含校验位)中1的个数为偶数。即:如果数据位中1的个数是偶数,则校验位为“0”,个数为奇数则校验位为”1”。数据和校验位发送给接受方后,接收方再次对数据位和校验位中1的个数进行统计,如果为偶数则校验通过,表示此次传输过程未发生错误。如果不是偶数,则表示有错误发生,此时接收方可以向发送方发送请求,要求重新发送一遍数据。

  优缺点:奇偶校验的检错率只有50%,只有奇数个数据位发生变化能检测到,如果偶数个数据位发生变化则不会被发现。奇偶校验每传输一个字节都需要加一位校验位,会降低传输效率。奇偶校验只能发现错误,但不能纠正错误。

1.2、停止位位数

  常用格式为1位停止位,但在串口助手里面会见到2位停止位和1.5位停止位的设置方式,所以会增加这个设置。

  停止位其实就是将数据线拉高表示停止传输数据,并且为下一次数据传输做准备。1位和2位停止位的区别在于数据线拉高持续的时间长短不同。以上节分析为准,比如波特率9600bit/s,时钟频率为50MHz时,约5208个时钟传输一位数据,那么1位停止位时高电平持续5208个时钟,2位停止位时高电平应持续52082个时钟周期,如果是1.5位,则高电平持续时间为52081.5即可。

  理论上需要检测停止位数据位数,但是有效数据在停止位之前就已经接收完了,停止位只是将数据线拉高一段时间,结束本次数据传输,为下一次数据传输做准备。所以在接收数据时,其实可以不考虑停止位,下面会提供两种文件,其中一种是不考虑停止位接收的,另一种就是考虑停止位位数的。

2、设计思路

  书接上节,在计数器bps_cnt == BPS_CNT/2-1时采集来自数据线上的数据,但是这个条件会被多个电路作为判断条件,造成组合逻辑延迟过长,从而降低系统的最大时钟频率。所以此处使用一个寄存器read_flag来把该条件暂存一下,由于时序逻辑会延迟一个时钟,所以读取数据的条件read_flag可以改为bps_cnt == BPS_CNT/2-2,对应代码如下:

reg  read_flag;
always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin//
        read_flag <= 0;
    end
    else begin
        read_flag <= add_bps_cnt && bps_cnt == BPS_CNT/2-2;
    end
end

  根据上一节内容,其实uart数据传输的格式已经明确。设计的整体思路不会变,只是计数器cnt的结束条件会受停止位的影响,接收到的数据不会马上输出,会与接收到的校验位进行比对后,如果正确才把接收到的数据输出。这里先给出包含停止位的设计,不包含停止的其实就是将这部分去掉而已。

  首先加入一个2位的parameter常量STOP_W用于表示停止位的位数,其值为2’b01表示1位停止位,2’b10表示2位停止位,2’b11表示1.5位停止位,2’b00无效。再加入一个2位的parameter常量CHECK_W用于表示校验方式,其值为2’b00或2’b11代表无校验位,2’b01表示奇校验,2’b10表示偶校验。

parameter       CHECK_W =       2'b00       ,//校验位,2'b00代表无校验位,2'b01表示奇校验,2'b10表示偶校验,2'b11无效。
parameter       STOP_W  =       2'b11        //停止位,2'b01表示1位停止位,2'b10表示2位停止位,2'b11表示1.5位停止位;

  接下来就是确定计数器bps_cnt和计数器cnt的结束条件了,计数器cnt的结束条件会根据停止位的位数不同分为两种情况,如果停止位为整数,那么计数器cnt计数到DATA_W + (^CHECK_W) + ((STOP_W2’b11) ? 1 : STOP_W)时结束,其中DATA_W表示数据传输位数,(^CHECK_W) 为1表示有校验位,否则表示无校验位,当停止位为整数时,STOP_W的值就是停止位位数。当停止位为1.5位(STOP_W2’b11)时,计数器cnt需要计数到DATA_W + (^CHECK_W) + ((STOP_W==2’b11) ? 1 : STOP_W)并且read_flag为高电平时计数器cnt清零。

  通过下面代码实现上述两种情况的生成,并且利用clogb2函数自动计算出计数器cnt位宽。

localparam      CNT_NUM   =     DATA_W + (^CHECK_W) + ((STOP_W==2'b11) ? 1 : STOP_W);//计数器计数值;
always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin//
        cnt <= {{CNT_NUM_W}{1'b0}};
    end
    else if(end_cnt)begin
        cnt <= {{CNT_NUM_W}{1'b0}};
    end
    else if(end_bps_cnt)begin
        cnt <= cnt + {{{CNT_NUM_W-1}{1'b0}},1'b1};
    end
end
//根据停止位的不同生成不同的计数器结束条件;
generate
    if(STOP_W == 2'b11)//1.5位停止位,因为CNT_NUM没有包含起始位,所以要等计数器计数到CNT_NUM时清零;
        assign end_cnt = cnt== CNT_NUM && read_flag;
    else//停止位为1位或者2位;
        assign end_cnt = end_bps_cnt && cnt== CNT_NUM;
endgenerate

  另外就是计数器bps_cnt的条件应该变为end_bps_cnt || end_cnt,否则停止位不为整数时,该计数器无法清零。

  接下来就是处理校验位了,整体思路通过判断^CHECK_W是否有效,利用generate if() 结构综合不同电路,从而节省资源。如果^CHECK_W表示没有校验位,则综合时不生成接收校验位的相关电路,根据校验类型生成相应接收电路,从而节约资源。

  如果有校验位,则在计数器cnt计数到DATA_W+1时提取校验位,在计数器cnt计数结束的时候,对接收到的数据和校验位进行比较,当校验为奇校验时,应满足^rx_data == ~check表示接收到的数据是正确的,将数据输出,否则数据传输错误,不更新输出数据。当校验位为偶校验时应满足^rx_data == check表示接收到的数据是正确的,将数据输出,否则数据传输错误,不更新输出数据。没有校验时,直接将接收到的数据输出,另外输出数据的指示信号也要与输出的数据对齐。本部分对应代码如下:

generate
    if(CHECK_W == 2'b01)begin//如果是奇校验,则综合这部分电路。
        always@(posedge clk or negedge rst_n)begin
            if(rst_n==1'b0)begin//
                check <= 0;
            end//接收校验位;
            else if(cnt==DATA_W + 1 && read_flag)begin
                check <= uart_rx_ff2;
            end
        end
        //当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
        always@(posedge clk or negedge rst_n)begin
            if(rst_n==1'b0)begin//
                rx_out <= 0;
            end
            else if(rx_vld && ^rx_data == ~check)begin//当校验正确时,将接收到的数据输出;
                rx_out <= rx_data;
            end
        end
        //在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
        always@(posedge clk or negedge rst_n)begin
            if(rst_n==1'b0)begin
                rx_out_vld <= 1'b0;
            end
            else begin//将数据有效指示信号和校验结果相与后输出;
                rx_out_vld <= rx_vld && ^rx_data == ~check;
            end
        end
    end
    else if(CHECK_W == 2'b10)begin//如果是偶校验,则综合这部分代码;
        always@(posedge clk or negedge rst_n)begin
            if(rst_n==1'b0)begin//
                check <= 0;
            end//接收校验位;
            else if(cnt==DATA_W + 1 && read_flag)begin
                check <= uart_rx_ff2;
            end
        end
        //当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
        always@(posedge clk or negedge rst_n)begin
            if(rst_n==1'b0)begin//
                rx_out <= 0;
            end
            else if(rx_vld && ^rx_data == check)begin//当校验正确时,将接收到的数据输出;
                rx_out <= rx_data;
            end
        end
        //在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
        always@(posedge clk or negedge rst_n)begin
            if(rst_n==1'b0)begin
                rx_out_vld <= 1'b0;
            end
            else begin//将数据有效指示信号和校验结果相与后输出;
                rx_out_vld <= rx_vld && ^rx_data == check;
            end
        end
    end
    else begin//如果没有校验位,则综合这部分代码。
        //当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
        always@(posedge clk or negedge rst_n)begin
            if(rst_n==1'b0)begin//
                rx_out <= 0;
            end
            else if(rx_vld)begin//如果没有校验位,将接收到的数据直接输出;
                rx_out <= rx_data;
            end
        end
        //在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
        always@(posedge clk or negedge rst_n)begin
            if(rst_n==1'b0)begin
                rx_out_vld <= 1'b0;
            end
            else begin//将接收到的数据有效指示信号与数据对齐输出;
                rx_out_vld <= rx_vld;
            end
        end
    end
endgenerate

  不包含停止位的就是将上述停止位相关的电路去掉即可。

3、参考代码

  包含检测停止位的参考代码如下所示使用该模块时,只需要修改5个parameter变量即可,当rx_out_vld为高电平时,rx_out的数据就是串口接收到的数。

//--###############################################################################################
//--# Designer : 发送一位数据所需系统时钟数计算方式BPS_CNT = 1000_000_000/(Tclk*比特率),
//Tclk是系统时钟周期,单位ns。
//--###############################################################################################
module uart_rx #(
    parameter       FLCK    =       50_000_000  ,//系统时钟频率,默认50MHZ;
    parameter       BPS     =       9600        ,//串口波特率;
    parameter       DATA_W  =       8           ,//接收数据位数以及输出数据位宽;
    parameter       CHECK_W =       2'b00       ,//校验位,2'b00代表无校验位,2'b01表示奇校验,2'b10表示偶校验,2'b11无效。
    parameter       STOP_W  =       2'b11        //停止位,2'b01表示1位停止位,2'b10表示2位停止位,2'b11表示1.5位停止位;
)(
    input                           clk         ,//系统工作时钟50MHZ
    input                           rst_n       ,//系统复位信号,低电平有效

    input                           uart_rx     ,//UART接口输入信号
    output reg  [DATA_W-1:0]        rx_out      ,//数据输出信号
    output reg                      rx_out_vld   //数据有效指示信号
    );
    
    localparam      BPS_CNT   =     FLCK/BPS;//波特率为9600bit/s,当波特率为115200bit/s时,DATA_115200==434;
    localparam      BPS_CNT_W =     clogb2(BPS_CNT-1);//根据BPS_CNT调用函数自动计算计数器bps_cnt位宽;
    localparam      CNT_NUM   =     DATA_W + (^CHECK_W) + ((STOP_W==2'b11) ? 1 : STOP_W);//计数器计数值;
    localparam      CNT_NUM_W =     clogb2(CNT_NUM);//根据计数器cnt的值,利用函数自动计算此计数器的位宽;

    reg                             rx_vld          ;//表示接收完一组串口发来的数据了;
    reg                             check           ;//接收的校验位数据;
    reg                             uart_rx_ff0     ;
    reg                             uart_rx_ff1     ;
    reg                             uart_rx_ff2     ;
    reg                             flag            ;
    reg     [BPS_CNT_W-1:0]         bps_cnt         ;
    reg     [CNT_NUM_W-1:0]         cnt             ;
    reg     [DATA_W-1:0]            rx_data         ;

    wire                            end_cnt         ;
    wire                            add_bps_cnt     ;
    wire                            end_bps_cnt     ;

    /******************注释开始******************
    自动计算信号位宽;
     ******************注释结束******************/
    function integer   clogb2(input integer depth);begin
        if(depth==0)
            clogb2 = 1;
        else if(depth!=0)
            for(clogb2=0;depth>0;clogb2=clogb2+1)
                depth=depth>>1;
        end
    endfunction

    /******************注释开始******************
    接收一位数据所用时间计数器bps_cnt,初始值为0,当接收到数据时进行计数,
    当一位数据接收完成时清零;
    ******************注释结束******************/
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            bps_cnt <= {{BPS_CNT_W}{1'b0}};
        end
        else if(add_bps_cnt)begin
            if(end_bps_cnt || end_cnt)
                bps_cnt <= {{BPS_CNT_W}{1'b0}};
            else
                bps_cnt <= bps_cnt + {{{BPS_CNT_W-1}{1'b0}},1'b1};
        end
    end

    assign add_bps_cnt = flag;
    assign end_bps_cnt = add_bps_cnt && bps_cnt==BPS_CNT-1;

    //在每位数据传输的中部读取数据,所以设立一个读取数据的标志位增强时序。
    //由于该标志信号是时序电路,故提前一个时钟为(BPS_CNT/2-1)-1=BPS_CNT/2-2
    reg  read_flag;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//
            read_flag <= 0;
        end
        else begin
            read_flag <= add_bps_cnt && bps_cnt == BPS_CNT/2-2;
        end
    end

    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//
            cnt <= {{CNT_NUM_W}{1'b0}};
        end
        else if(end_cnt)begin
            cnt <= {{CNT_NUM_W}{1'b0}};
        end
        else if(end_bps_cnt)begin
            cnt <= cnt + {{{CNT_NUM_W-1}{1'b0}},1'b1};
        end
    end
    //根据停止位的不同生成不同的计数器结束条件;
    generate
        if(STOP_W == 2'b11)//1.5位停止位,因为CNT_NUM没有包含起始位,所以要等计数器计数到CNT_NUM时清零;
            assign end_cnt = cnt== CNT_NUM && read_flag;
        else//停止位为1位或者2位;
            assign end_cnt = end_bps_cnt && cnt== CNT_NUM;
    endgenerate

    /******************注释开始******************
    PC端相对应于FPGA为异步接口,为预防亚稳态产生,对接收数据进行打两拍处理,由于需要采集信号下降沿,故打三拍处理;
    ******************注释结束******************/
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//三个寄存器组成移位寄存器,初始化为0;
            {uart_rx_ff2,uart_rx_ff1,uart_rx_ff0} <= 3'd0;
        end
        else begin//时钟上升沿时,将uart_rx信号移入移位寄存器,其余位左移一位;
            {uart_rx_ff2,uart_rx_ff1,uart_rx_ff0} <= {uart_rx_ff1,uart_rx_ff0,uart_rx};
        end
    end

    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            flag <= 1'b0;
        end
        else if(uart_rx_ff2==1 && uart_rx_ff1==0)begin//取UART_RX信号下降沿
            flag <= 1'b1;
        end
        else if(end_cnt)begin//一组数据接收完毕;
            flag <= 1'b0;
        end
    end

    //在中间时刻对输入数据进行采集,并且将数据存入rx_data;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            rx_data <= {{DATA_W}{1'b0}};
        end
        else if(cnt>=1 && cnt<=DATA_W && read_flag)begin
            rx_data[cnt-1] <= uart_rx_ff2;
        end
    end

    //在接收完数据后,指示产生rx_data信号有效;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            rx_vld <= 1'b0;
        end
        else begin
            rx_vld <= (end_cnt);
        end
    end

    generate
        if(CHECK_W == 2'b01)begin//如果是奇校验,则综合这部分电路。
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin//
                    check <= 0;
                end//接收校验位;
                else if(cnt==DATA_W + 1 && read_flag)begin
                    check <= uart_rx_ff2;
                end
            end
            //当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin//
                    rx_out <= 0;
                end
                else if(rx_vld && ^rx_data == ~check)begin//当校验正确时,将接收到的数据输出;
                    rx_out <= rx_data;
                end
            end
            //在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin
                    rx_out_vld <= 1'b0;
                end
                else begin//将数据有效指示信号和校验结果相与后输出;
                    rx_out_vld <= rx_vld && ^rx_data == ~check;
                end
            end
        end
        else if(CHECK_W == 2'b10)begin//如果是偶校验,则综合这部分代码;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin//
                    check <= 0;
                end//接收校验位;
                else if(cnt==DATA_W + 1 && read_flag)begin
                    check <= uart_rx_ff2;
                end
            end
            //当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin//
                    rx_out <= 0;
                end
                else if(rx_vld && ^rx_data == check)begin//当校验正确时,将接收到的数据输出;
                    rx_out <= rx_data;
                end
            end
            //在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin
                    rx_out_vld <= 1'b0;
                end
                else begin//将数据有效指示信号和校验结果相与后输出;
                    rx_out_vld <= rx_vld && ^rx_data == check;
                end
            end
        end
        else begin//如果没有校验位,则综合这部分代码。
            //当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin//
                    rx_out <= 0;
                end
                else if(rx_vld)begin//如果没有校验位,将接收到的数据直接输出;
                    rx_out <= rx_data;
                end
            end
            //在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin
                    rx_out_vld <= 1'b0;
                end
                else begin//将接收到的数据有效指示信号与数据对齐输出;
                    rx_out_vld <= rx_vld;
                end
            end
        end
    endgenerate

endmodul

  不包含停止位的参考代码如下:

//--###############################################################################################
//--# Designer : 发送一位数据所需系统时钟数计算方式BPS_CNT = 1000_000_000/(Tclk*比特率),
//Tclk是系统时钟周期,单位ns。
//--###############################################################################################
module uart_rx #(
    parameter       FLCK    =       50_000_000  ,//系统时钟频率,默认50MHZ;
    parameter       BPS     =       9600        ,//串口波特率;
    parameter       DATA_W  =       8           ,//接收数据位数以及输出数据位宽;
    parameter       CHECK_W =       2'b00        //校验位,2'b00代表无校验位,2'b01表示奇校验,2'b10表示偶校验,2'b11无效。
)(
    input                           clk         ,//系统工作时钟50MHZ
    input                           rst_n       ,//系统复位信号,低电平有效

    input                           uart_rx     ,//UART接口输入信号
    output reg  [DATA_W-1:0]        rx_out      ,//数据输出信号
    output reg                      rx_out_vld   //数据有效指示信号
    );
    
    localparam      BPS_CNT   =     FLCK/BPS;//波特率为9600bit/s,当波特率为115200bit/s时,DATA_115200==434;
    localparam      BPS_CNT_W =     clogb2(BPS_CNT-1);//根据BPS_CNT调用函数自动计算计数器bps_cnt位宽;
    localparam      CNT_NUM   =     DATA_W + (^CHECK_W);//计数器计数值;
    localparam      CNT_NUM_W =     clogb2(CNT_NUM);//根据计数器cnt的值,利用函数自动计算此计数器的位宽;

    reg                             rx_vld          ;//表示接收完一组串口发来的数据了;
    reg                             check           ;//接收的校验位数据;
    reg                             uart_rx_ff0     ;
    reg                             uart_rx_ff1     ;
    reg                             uart_rx_ff2     ;
    reg                             flag            ;
    reg     [BPS_CNT_W-1:0]         bps_cnt         ;
    reg     [CNT_NUM_W-1:0]         cnt             ;
    reg     [DATA_W-1:0]            rx_data         ;

    wire                            add_cnt         ;
    wire                            end_cnt         ;
    wire                            add_bps_cnt     ;
    wire                            end_bps_cnt     ;

    /******************注释开始******************
    自动计算信号位宽;
     ******************注释结束******************/
    function integer   clogb2(input integer depth);begin
        if(depth==0)
            clogb2 = 1;
        else if(depth!=0)
            for(clogb2=0;depth>0;clogb2=clogb2+1)
                depth=depth>>1;
        end
    endfunction

    /******************注释开始******************
    接收一位数据所用时间计数器bps_cnt,初始值为0,当接收到数据时进行计数,
    当一位数据接收完成时清零;
    ******************注释结束******************/
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            bps_cnt <= {{BPS_CNT_W}{1'b0}};
        end
        else if(add_bps_cnt)begin
            if(end_bps_cnt)
                bps_cnt <= {{BPS_CNT_W}{1'b0}};
            else
                bps_cnt <= bps_cnt + {{{BPS_CNT_W-1}{1'b0}},1'b1};
        end
    end

    assign add_bps_cnt = flag;
    assign end_bps_cnt = add_bps_cnt && bps_cnt==BPS_CNT-1;

    //在每位数据传输的中部读取数据,所以设立一个读取数据的标志位增强时序。
    //由于该标志信号是时序电路,故提前一个时钟为(BPS_CNT/2-1)-1=BPS_CNT/2-2
    reg  read_flag;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//
            read_flag <= 0;
        end
        else begin
            read_flag <= add_bps_cnt && bps_cnt == BPS_CNT/2-2;
        end
    end

    //接收一组数据所用时间;    
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//
            cnt <= 0;
        end
        else if(add_cnt)begin
            if(end_cnt)
                cnt <= 0;
            else
                cnt <= cnt + 1;
        end
    end
    
    assign add_cnt = end_bps_cnt;
    assign end_cnt = add_cnt && cnt ==  CNT_NUM;

    /******************注释开始******************
    PC端相对应于FPGA为异步接口,为预防亚稳态产生,对接收数据进行打两拍处理,由于需要采集信号下降沿,故打三拍处理;
    ******************注释结束******************/
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//三个寄存器组成移位寄存器,初始化为0;
            {uart_rx_ff2,uart_rx_ff1,uart_rx_ff0} <= 3'd0;
        end
        else begin//时钟上升沿时,将uart_rx信号移入移位寄存器,其余位左移一位;
            {uart_rx_ff2,uart_rx_ff1,uart_rx_ff0} <= {uart_rx_ff1,uart_rx_ff0,uart_rx};
        end
    end

    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            flag <= 1'b0;
        end
        else if(uart_rx_ff2==1 && uart_rx_ff1==0)begin//取UART_RX信号下降沿
            flag <= 1'b1;
        end
        else if(end_cnt)begin//一组数据接收完毕;
            flag <= 1'b0;
        end
    end

    //在中间时刻对输入数据进行采集,并且将数据存入rx_data;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            rx_data <= {{DATA_W}{1'b0}};
        end
        else if(cnt>=1 && cnt<=DATA_W && read_flag)begin
            rx_data[cnt-1] <= uart_rx_ff2;
        end
    end

    //在接收完数据后,指示产生rx_data信号有效;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            rx_vld <= 1'b0;
        end
        else begin
            rx_vld <= (end_cnt);
        end
    end

    generate
        if(CHECK_W == 2'b01)begin//如果是奇校验,则综合这部分电路。
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin//
                    check <= 0;
                end//接收校验位;
                else if(cnt==DATA_W + 1 && read_flag)begin
                    check <= uart_rx_ff2;
                end
            end
            //当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin//
                    rx_out <= 0;
                end
                else if(rx_vld && ^rx_data == ~check)begin//当校验正确时,将接收到的数据输出;
                    rx_out <= rx_data;
                end
            end
            //在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin
                    rx_out_vld <= 1'b0;
                end
                else begin//将数据有效指示信号和校验结果相与后输出;
                    rx_out_vld <= rx_vld && ^rx_data == ~check;
                end
            end
        end
        else if(CHECK_W == 2'b10)begin//如果是偶校验,则综合这部分代码;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin//
                    check <= 0;
                end//接收校验位;
                else if(cnt==DATA_W + 1 && read_flag)begin
                    check <= uart_rx_ff2;
                end
            end
            //当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin//
                    rx_out <= 0;
                end
                else if(rx_vld && ^rx_data == check)begin//当校验正确时,将接收到的数据输出;
                    rx_out <= rx_data;
                end
            end
            //在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin
                    rx_out_vld <= 1'b0;
                end
                else begin//将数据有效指示信号和校验结果相与后输出;
                    rx_out_vld <= rx_vld && ^rx_data == check;
                end
            end
        end
        else begin//如果没有校验位,则综合这部分代码。
            //当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin//
                    rx_out <= 0;
                end
                else if(rx_vld)begin//如果没有校验位,将接收到的数据直接输出;
                    rx_out <= rx_data;
                end
            end
            //在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin
                    rx_out_vld <= 1'b0;
                end
                else begin//将接收到的数据有效指示信号与数据对齐输出;
                    rx_out_vld <= rx_vld;
                end
            end
        end
    endgenerate

endmodule

4、综合及仿真测试

  无校验 1位停止位时综合所使用逻辑资源及RTL电路图:
在这里插入图片描述

图1 校验位和停止位设

  利用Quartus对改源文件进行综合后,如下图所示,消耗62个逻辑块,40个LUT。
基于FPGA的UART接收模快(全模式设置)_第1张图片

图2 工程综合结果

  综合后的RTL电路图如下图所示:
基于FPGA的UART接收模快(全模式设置)_第2张图片

图3 RTL电路图

  奇校验 1.5位停止位时综合所使用逻辑资源及RTL电路图:
在这里插入图片描述

图4 参数设置

  利用Quartus对改源文件进行综合后,如下图所示,消耗63个逻辑块、41个LUT。
基于FPGA的UART接收模快(全模式设置)_第3张图片

图5 Quartus综合结果

基于FPGA的UART接收模快(全模式设置)_第4张图片

图6 RTL电路图

  对其进行时序约束后,重新综合结果如下,最大时钟频率为173.73MHz,大于50MHz,该系统能够正常工作。
基于FPGA的UART接收模快(全模式设置)_第5张图片

图7 时序约束

  包含停止位接收Testbetch参考:

`timescale 1 ns/1 ns
module uart_rx_test();
    parameter	CYCLE		= 20;//The unit is ns. The default value is 10ns;
    parameter	RST_TIME	= 10;//Reset time: Reset 3 clock widths by default;
    parameter	STOP_TIME	= 1000;//Time for simulation running after reset (unit: clock cycle). Simulation stops after 1000 clocks are run by default;

    // uart_rx Parameters
    parameter   FCLK        = 50_000_000;//系统时钟频率;
    parameter   BPS         = 9600      ;//串口波特率;
    parameter   BPS_CNT     = FCLK/BPS  ;//波特率对应时钟数,不用手动修改该参数;
    parameter   DATA_W      = 8         ;//接收数据位数以及输出数据位宽;
    parameter   CHECK_W     = 2'b01     ;//校验位,2'b00代表无校验位,2'b01表示奇校验,2'b10表示偶校验,2'b11无效。
    parameter   STOP_W      = 2'b11     ;//停止位,2'b01表示1位停止位,2'b10表示2位停止位,2'b11表示1.5位停止位;

    // uart_rx Inputs
    reg                     clk         ;
    reg                     rst_n       ;
    reg                     uart_tx     ;

    // uart_rx Outputs
    wire  [DATA_W-1:0]      rx_out      ;
    wire                    rx_out_vld  ;

    //例化串口接收模块;
    uart_rx #(
        .FLCK       ( FCLK          ),
        .BPS        ( BPS           ),
        .DATA_W     ( DATA_W        ),
        .CHECK_W    ( CHECK_W       ),
        .STOP_W     ( STOP_W        ))
    u_uart_rx (
        .clk        ( clk           ),
        .rst_n      ( rst_n         ),
        .uart_rx    ( uart_tx       ),
        .rx_out     ( rx_out        ),
        .rx_out_vld ( rx_out_vld    )
    );

    //The local clock is generated at 100 MB;
    initial begin
        clk = 0; 
        forever #(CYCLE/2) clk=~clk;
    end

    //Generate reset signal;
    initial begin
        rst_n = 1;
        #2; rst_n = 0;
        #(RST_TIME*CYCLE);//复位完成;
        rst_n = 1;
    end

    //Input signal din assignment method;
    initial begin
        #1;uart_tx = 1; //初始化时输入高电平;
        #(100*CYCLE);   //Start assigning values;
        tx(8'ha5);      //以串口形式发送8'h5a;
        #(500*CYCLE);   //发送完成后延迟500个时钟;
        tx(8'h57);      //之后发送数据8'h57;
        #(500*CYCLE);   //发送完成后延迟500个时钟;
        $stop;          //Stop simulation;
    end

    //模拟串口发送函数,1位起始位,1位停止位,无校验位,8位数据,先发低位;
    integer i;//用于控制循环次数;
    task tx(
        input   [DATA_W-1:0]   data //串口待发送数据;
    );
        begin
            @(posedge clk);//延迟一个时钟后发送起始位;
            #1; uart_tx = 1'b0;
            repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
            for(i=0 ; i<8 ; i=i+1)begin
                #1; uart_tx = data[i];
                repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
            end
            if(CHECK_W == 2'b01)begin
                #1;uart_tx = ~(^data);//奇校验时,发送数据;
                repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
            end
            else if(CHECK_W == 2'b10)begin
                #1;uart_tx = (^data);//偶校验时,发送数据;
                repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
            end
            @(posedge clk);//延迟一个时钟后发送停止位;
            #1; uart_tx = 1'b1;
            if(STOP_W == 2'b01)//1位停止位;
                repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
            else if(STOP_W == 2'b10)//2位停止位;
                repeat(2*BPS_CNT) @(posedge clk);//延迟2*BPS_CNT个时钟;
            else if(STOP_W == 2'b11)//1.5位停止位;
                repeat(BPS_CNT*3/2) @(posedge clk);//延迟1.5*BPS_CNT个时钟;
        end
    endtask

endmodule

  如下图所示,以奇校验、1.5位停止位的产生串口测试信号;
在这里插入图片描述

  TestBetch通过调用rx()任务以上述格式先后发送8’ha5和8’h57给串口接收模块。
基于FPGA的UART接收模快(全模式设置)_第6张图片

  运行上述仿真,串口接收模块接收波形如下所示,rx_out准确接收到上述发送信号,如图8所示。
基于FPGA的UART接收模快(全模式设置)_第7张图片

图8 串口接收奇校验、1.5位停止位时序

  在计数器cnt为数据位8+1=9并且read_falg为高电平时采集uart_rx_ff2数据作为校验位数据。
基于FPGA的UART接收模快(全模式设置)_第8张图片

图9 校验位采集信号

基于FPGA的UART接收模快(全模式设置)_第9张图片

图10 校验位采集信号

  其余仿真就不介绍了,之前那一节仿真时序已经做了详细讲解,此工程也会直接提供,使用时只需要配置几个parameter参数即可,如果想要修改仿真发送的数据,也只需要修改那几个parameter变量,以及调用rx()任务即可,其余部分不需要做任何修改。

  不检测停止位的代码测试参考文件如下:

  下面是不检测停止位的仿真,对比前面仿真可知,这种方式会在校验位传输完后就输出接收到的数据,会快那么一点,最后就不上板了(目前身边没有altera下载器)。

`timescale 1 ns/1 ns
module uart_rx_test();
    parameter	CYCLE		= 20;//The unit is ns. The default value is 10ns;
    parameter	RST_TIME	= 10;//Reset time: Reset 3 clock widths by default;
    parameter	STOP_TIME	= 1000;//Time for simulation running after reset (unit: clock cycle). Simulation stops after 1000 clocks are run by default;
    // uart_rx Parameters
    parameter   FCLK        = 50_000_000;//系统时钟频率;
    parameter   BPS         = 9600      ;//串口波特率;
    parameter   BPS_CNT     = FCLK/BPS  ;//波特率对应时钟数,不用手动修改该参数;
    parameter   DATA_W      = 8         ;//接收数据位数以及输出数据位宽;
    parameter   CHECK_W     = 2'b01     ;//校验位,2'b00代表无校验位,2'b01表示奇校验,2'b10表示偶校验,2'b11无效。
    parameter   STOP_W      = 2'b11     ;//停止位,2'b01表示1位停止位,2'b10表示2位停止位,2'b11表示1.5位停止位;
    
    // uart_rx Inputs
    reg                     clk         ;
    reg                     rst_n       ;
    reg                     uart_tx     ;
    
    // uart_rx Outputs
    wire  [DATA_W-1:0]      rx_out      ;
    wire                    rx_out_vld  ;
    
    //例化串口接收模块;
    uart_rx #(
        .FLCK       ( FCLK          ),
        .BPS        ( BPS           ),
        .DATA_W     ( DATA_W        ),
        .CHECK_W    ( CHECK_W       ),
        .STOP_W     ( STOP_W        ))
    u_uart_rx (
        .clk        ( clk           ),
        .rst_n      ( rst_n         ),
        .uart_rx    ( uart_tx       ),
        .rx_out     ( rx_out        ),
        .rx_out_vld ( rx_out_vld    )
    );
    
    //The local clock is generated at 100 MB;
    initial begin
        clk = 0;
        forever #(CYCLE/2) clk=~clk;
    end
    
    //Generate reset signal;
    initial begin
        rst_n = 1;
        #2; rst_n = 0;
        #(RST_TIME*CYCLE);//复位完成;
        rst_n = 1;
    end
    
    //Input signal din assignment method;
    initial begin
        #1;uart_tx = 1; //初始化时输入高电平;
        #(100*CYCLE);   //Start assigning values;
        tx(8'ha5);      //以串口形式发送8'h5a;
        #(500*CYCLE);   //发送完成后延迟500个时钟;
        tx(8'h57);      //之后发送数据8'h57;
        #(500*CYCLE);   //发送完成后延迟500个时钟;
        $stop;          //Stop simulation;
    end
    
    //模拟串口发送函数,1位起始位,1位停止位,无校验位,8位数据,先发低位;
    integer i;//用于控制循环次数;
    task tx(
        input   [DATA_W-1:0]   data //串口待发送数据;
    );
        begin
            @(posedge clk);//延迟一个时钟后发送起始位;
            #1; uart_tx = 1'b0;
            repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
            for(i=0 ; i<8 ; i=i+1)begin
                #1; uart_tx = data[i];
                repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
            end
            if(CHECK_W == 2'b01)begin
                #1;uart_tx = ~(^data);//奇校验时,发送数据;
                repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
            end
            else if(CHECK_W == 2'b10)begin
                #1;uart_tx = (^data);//偶校验时,发送数据;
                repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
            end
            @(posedge clk);//延迟一个时钟后发送停止位;
            #1; uart_tx = 1'b1;
            if(STOP_W == 2'b01)//1位停止位;
                repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
            else if(STOP_W == 2'b10)//2位停止位;
                repeat(2*BPS_CNT) @(posedge clk);//延迟2*BPS_CNT个时钟;
            else if(STOP_W == 2'b11)//1.5位停止位;
                repeat(BPS_CNT*3/2) @(posedge clk);//延迟1.5*BPS_CNT个时钟;
        end
    endtask
endmodule

基于FPGA的UART接收模快(全模式设置)_第10张图片

图11 串口接收奇校验、1.5位停止位时序

  这两个工程的源文件在后台回复uart_rx即可。
基于FPGA的UART接收模快(全模式设置)_第11张图片

图12 两个工程文件

  文件目录如下:
基于FPGA的UART接收模快(全模式设置)_第12张图片

图13 文件目录

  该工程所使用的软件版本为Quartus 18.1,获取工程后,可以直接联合仿真,另外我已经将波形设置保存,进入modelsim之后,在wave页面删除自动加入的信号,最终如下图所示:
基于FPGA的UART接收模快(全模式设置)_第13张图片

图14 清空wave界面

  然后依次点击fFile→Load→Macro File…
基于FPGA的UART接收模快(全模式设置)_第14张图片

图15 查找wave.do文件

  如下图所示,选中打开wave.do文件。
基于FPGA的UART接收模快(全模式设置)_第15张图片

图16 选中打开wave.do文件

  打开结果如下所示:
基于FPGA的UART接收模快(全模式设置)_第16张图片

图17 打开wave.do结果

  之后信号就如同之前那样设置了,就可以清除当前仿真,重新进行自己的仿真了

你可能感兴趣的:(fpga开发,单片机,嵌入式硬件)