【Verilog串口篇2】UART接收模块及Testbench仿真程序

【Verilog串口篇2】UART接收模块及Testbench仿真程序

本文讲解FPGA串口通信的 接收模块 Verilog实现,即根据数据链路层协议,完成起始位、数据位、校验位和停止位的接收,通信参数如波特率可由用户配置。

1、接口描述
直接从模块接口描述说起,咋一看和发送模块十分相似,细看二者区别也很明显,发送是“并入串出”而接收是“串入并出”

module UARTRecv #(parameter
    CLK_FREQ    = 50_000_000,  // 时钟频率
    BAUD_RATE   = 115200,  // 波特率
    WORD_LENGTH = 8,       // 数据位
    STOP_BITS   = 2,       // 停止位
                           // 2 仅用于发送,多发一个,防止接收端漏帧
    PARITY      = "NONE"   // ODD, EVEN 鸡肋,弃之
)
(
    input Clock,  // Clock = CLK_FREQ
    input nRst,
    input Rx,
    output reg [WORD_LENGTH-1:0] Word,  // Shift Reg, 实时存储接收到的数据位
    output reg Cplt  // 接收完成,一个时钟周期高电平
);

2、起始信号
对于异步通信,接收方根本不知道发送方何时开始发送,这就需要发送方先通知接收方“我要开始发送了,请做好接收准备”。具体怎么通知呢?在空闲状态,线路为“1”。发送方发送“0”作为起始位,标识数据传输开始。因此接收方需要检测线路从“1”到“0”的跳变,也即下降沿,即为起始信号。 程序中通过移位寄存器缓存四位数据,若前两位为“1”,后两位为“0”,则检测到下降沿。

/* 捕获接收起始信号:下降沿 */
wire recvStart;   // 起始标志
reg [3:0] rxBuf;  // 用于判断下降沿
always @(posedge Clock or negedge nRst) begin
    if (!nRst) 
        rxBuf <= 4'b1111;
    else 
        rxBuf <= {rxBuf[2:0],Rx};
    end
assign recvStart = rxBuf[3] & rxBuf[2] & ~rxBuf[1] & ~rxBuf[0];

3、采样点
既已捕获到起始信号,再来个波特率时钟,就可以接收数据了,就像发送一样,So Easy!深入思考一下,发送是个连续过程,而接收我们只是在波特率时钟处读取数据,是个采样过程,既然是采样,只采样一次是否不妥?这里,将每位平均分成九段,在每段的中点都进行采样,这样对一个位就采样了九次。至少从概率论角度分析,可以减少干扰的影响。生成采样点,程序中:SMP_CLK = CLK_FREQ / BAUD_RATE / 9 - 1,为什么这么计算这里就不啰嗦了,重点强调一下 -1 很有必要,这样可以使采样也即接收尽快完成,不至于在连续接收时丢帧。

/* 采样时钟计数及采样点生成 */
reg [log2(SMP_CLK)-1:0] sampleClk;  // 以采样周期计数,非波特率周期,九倍关系
reg samplePoint;  // 计数中点为采样点
always @(posedge Clock or negedge nRst) begin
    if (!nRst) begin
        sampleClk <= 'd0;
        samplePoint <= 1'b0;
    end
    else if (recvFlag) begin
        sampleClk <= (sampleClk == SMP_CLK) ? 'd0 : (sampleClk + 1'b1);
        samplePoint <= (sampleClk == SMP_CLK >> 1) ? 1'b1 : 1'b0;
    end
    else begin
        sampleClk <= 'd0;
        samplePoint <= 1'b0;
    end
end

4、采样结果
下一步是计算采样结果,考虑到时钟偏移导致前后采样值的不确定性,只取中间三次采样值,看“1”个数多还是“0”个数多,即为采样结果。可惜机器不会“看”,计算能力倒是很强,还是转换成计算问题吧。采样结果:sampleResult[1]

/* 记录采样次数及采样结果 */
reg [3:0] sampleCnt;     // 采样计数,9 次(0->8)
reg [1:0] sampleResult;  // 采样结果,中间三个相加,取高位
always @(posedge Clock or negedge nRst) begin
    if (!nRst) begin
        sampleCnt <= 4'd0;
        sampleResult <= 2'b00;
    end
    else if (recvFlag) begin
        if (samplePoint) begin
            sampleCnt <= (sampleCnt == 4'd8) ? 4'd0 : (sampleCnt + 1'b1);
            case (sampleCnt)
                4'd0: sampleResult <= 2'd0;
                4'd3, 4'd4, 4'd5: sampleResult <= sampleResult + Rx;
                default: sampleResult <= sampleResult;
            endcase
        end
        else begin
            sampleCnt <= sampleCnt;
            sampleResult <= sampleResult;
        end
    end
    else begin
        sampleCnt <= 4'd0;
        sampleResult <= 2'b00;
    end
end

5、波特率时钟
再次回到波特率时钟,毕竟接收本质上也是由波特率时钟控制的,注意程序是 在第 7个(从 0 计数)采样点处生成波特率时钟,而不是等到最后,为什么呢?另外,发送是在位的头部生成波特率时钟,而接收是在尾部

/* 生成波特率时钟 */
wire bitClk;  // 波特率时钟
assign bitClk = samplePoint && (sampleCnt == 4'd7);  // Not 4'd8, 尽快完成,以免错过下一帧

6、接收状态机
万事俱备,正事却还没办,下面来说说接收过程。顺序执行状态机,直接贴上代码,再次声明,接收也是在波特率时钟控制下进行的。

/* 状态切换(固定格式) */
always @(posedge Clock or negedge nRst) begin
    if (!nRst) 
        cstate <= S_IDLE;
    else 
        cstate <= nstate;
end
/* 得到次态的组合逻辑,用 = 赋值 */
always @(*) begin
    case (cstate)
        S_IDLE: nstate = recvStart ? S_START : S_IDLE;
        S_START: nstate = bitClk ? S_WORD : S_START;
        S_WORD: nstate = count == WORD_LENGTH ? S_STOP : S_WORD;
        S_STOP: nstate = bitClk ? S_CPLT : S_STOP;
        S_CPLT: nstate = S_IDLE;
        default: nstate = S_IDLE;
    endcase
end
/* 状态输出(即相应状态需要执行的动作) */
always @(posedge Clock or negedge nRst) begin
    if (!nRst) begin
        count <= 'd0;
        Word <= 'd0;
        Cplt <= 1'b0;
    end 
    else begin
        case (cstate)  // Not nstate, 个人更易理解
            S_IDLE: begin 
                count <= 'd0;
                Cplt <= 1'b0;
            end
            S_WORD: begin
                // 采样结果移入寄存器,低位先发,故右移,用拼接实现
                Word <= bitClk ? {sampleResult[1],Word[WORD_LENGTH-1:1]} : Word;
                count <= bitClk ? count + 'd1 : count;
            end
            S_CPLT: Cplt <= 1'b1;
            default: Cplt <= 1'b0;  // 无意义
        endcase
    end
end

7、仿真程序
贴上代码,循环 10 次接收 3 个字节。

`timescale 1ns / 1ps
module testbench;
localparam
    CLK_FREQ    = 50_000_000,
    BAUD_RATE   = 115200,
    WORD_LENGTH = 8,
    STOP_BITS   = 2,
    PARITY      = "NONE";
reg clk, rst_n;
reg rx;
wire [WORD_LENGTH-1:0] recvWord;
wire recvCplt;
UARTRecv #(
    .CLK_FREQ(CLK_FREQ),
    .BAUD_RATE(BAUD_RATE),
    .WORD_LENGTH(WORD_LENGTH),
    .STOP_BITS(STOP_BITS),
    .PARITY(PARITY)
)
U0_UARTRecv(
    .Clock(clk),
    .nRst(rst_n),
    .Rx(rx),
    .Word(recvWord),
    .Cplt(recvCplt)
);
integer bitTime, idx, idxWord, idxBit;
reg [7:0] word [0:2];
initial begin
    clk = 0; rst_n = 0; rx = 1'b1; #10000; rst_n = 1; #10000;
    bitTime = 8680;  // 根据波特率设置位时间周期
    word[0] = 8'hfa; word[1] = 8'hff; word[2] = 8'ha5;
    for (idx=0; idx<10; idx=idx+1) begin  // 传输次数
        for (idxWord=0; idxWord<3; idxWord=idxWord+1) begin  // 传输字节
            rx = 1'b0; #bitTime; //start
            for (idxBit=0; idxBit<8; idxBit=idxBit+1) begin  // 传输位
                rx = word[idxWord][idxBit];
                #bitTime;
            end
            rx = 1'b1; #bitTime; //stop
        end
        #1000000;
    end
end
always begin
    #10 clk = ~clk;
end
endmodule
  • 1
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
奇偶校验是一种用于检测和纠正传输数据错误的方法,可以确保数据在传输过程中的完整性和准确性。奇偶校验算法通过对每个字符的二进制数进行计算,确定其是否为奇数或偶数,并将该信息添加到传输数据中。 测试奇偶校验的testbench需要模拟数据的传输和校验过程。首先,我们需要生成要传输的原始数据,包括字符和对应的二进制数。 然后,我们在实现奇偶校验算法的模块中添加一个testbench模块,该模块将读取传输的数据并进行奇偶校验的计算。在testbench中,我们需要模拟数据的传输和接收。我们可以随机选择一些要发送的字符,并将它们转换为二进制数。然后,我们使用奇偶校验算法来计算每个字符的校验位,将字符和校验位一起发送。 在接收端,testbench将模拟接收数据的过程。它将读取发送的数据,并使用奇偶校验算法进行校验。如果接收到的数据与发送时计算的校验位不匹配,则说明在传输过程中发生了错误,testbench可以通过打印错误信息或其他方式进行报告。 最后,我们需要验证testbench的正确性。我们可以使用不同的测试数据,包括一些已知的正确和错误数据,来测试奇偶校验的功能。如果testbench能够正确检测错误数据并报告错误,同时可以正确接收并校验正确的数据,则可以认为testbench是有效的。 在测试过程中,还可以考虑一些特殊情况,例如发送空数据或只包含一个字符的数据,以确保奇偶校验算法对于这些情况的处理也是正确的。 通过以上的步骤和测试,我们可以确保奇偶校验testbench的准确性和可靠性,同时也可以验证奇偶校验算法的功能是否实现正确。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

硬件拾遗

感谢道友

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值