串口(UART)接收模块-verilog实现-VCS仿真

一、要点

参考上一篇发送模块https://blog.csdn.net/a1254484594/article/details/100190162

二、实现

1.架构:使用有限状态机实现,主要包含两部分:数据采样、状态转移。

数据采样一般在每个数据位的中间采样,因为此时离上升沿或下降沿最远。

状态机可定义11个状态,对应到一帧数据中,依照时间顺序为:WAIT等待起始位状态,STARD起始位,D0、D1、D2、D3、 D4、D5、D6、D7 8bit数据,END停止位。使用一个计数器,定时一定波特率下每bit所需要的时间,在各状态间进行转移。

2.新建一个uart_rx.v文件,使用Verilog语言进行描述

`timescale 1ns/1ps
module uart_rx(
    input clk,
    input nrst,
    input rx_en,
    input rx_pin,
    output reg [7:0]rx_data,
    output reg rx_done
);

parameter INPUT_CLK = 125000000 , BUDO = 115200;
localparam B_CNT_MAX = (INPUT_CLK/BUDO)-1;//波特率计数器最大值

parameter [3:0]
    WAIT = 4'd12,   //等待起始位状态
    STARD= 4'd13,   //起始位
    END  = 4'd8,    //停止位
    D0 = 0, D1 = 1, D2 = 2, D3 = 3, D4 = 4, D5 = 5, D6 = 6, D7 = 7;//8bit数据

reg [3:0]CS;    //状态
reg [10:0]b_cnt; //用来产生波特率时钟

以上定义一个uart_tx模块,定义端口信号,定义所需参数、信号

//状态转移逻辑
always @(posedge clk) begin
    if (!nrst) begin//复位
        CS <= WAIT;
        b_cnt <= 'd0;
    end
    else if(rx_en) begin
        case(CS)
            WAIT:/*等待起始位*/
                begin
                    if(!rx_pin) CS <= STARD;//rx被拉低后进入起始位
                    else CS <= CS;
                end
            STARD:/*起始位*/
                begin
                     if (b_cnt == B_CNT_MAX) begin//计数满1bti时间,计数器清零,进入第一bit的接收
                        CS <= 4'd0; //转移到接收第一bit的状态
                        b_cnt <= 'd0;
                    end
                    else begin //计数中
                        b_cnt <= b_cnt + 1'b1;
                        CS <= CS;
                    end
                end
            END:/*停止位*/
                begin
                    if (b_cnt == B_CNT_MAX) begin//计数满1bti时间,计数器清零,进入等待状态
                        CS <= WAIT; //转移到等待状态
                        b_cnt <= 'd0;
                    end
                    else begin //计数中
                        b_cnt <= b_cnt + 1'b1;
                        CS <= CS;
                    end
                end
            D0,D1,D2,D3,D4,D5,D6,D7:/*采样数据*/
                begin
                    if (b_cnt == B_CNT_MAX) begin//计数满1bti时间,计数器清零,进入下一bit
                        CS <= CS + 1'b1; //转移到下一bit
                        b_cnt <= 'd0;
                    end
                    else begin //计数中
                        b_cnt <= b_cnt + 1'b1;
                        CS <= CS;
                    end
                end
            default:
                begin
                    CS <= CS;
                    b_cnt <= 'd0;
                end
        endcase
    end //end else if (rx_en)
end

状态转移部分,在rx_en有效时才进行转移操作,配合使用寄存器b_cnt计数,实现等待起始位、起始位、8各数据位、停止位的依次转移。

//根据CS状态和b_cnt计数器值采样rx_pin
always @(posedge clk) begin
    if (!nrst) begin//复位
        rx_data <= 8'd0;
        rx_done <= 1'b0;
    end
    else if (CS < 'd8) begin
        rx_done <= 1'b0;
        if (b_cnt == B_CNT_MAX/2)//在中点采样
            rx_data[CS] <= rx_pin;
        else
            rx_data <= rx_data;
    end
    else if (CS == 'd8) begin
        if (!rx_pin) //停止位错误
            rx_done <= 1'b0;
        else
            rx_done <= 1'b1;
    end
    else begin
        rx_data <= rx_data;
        rx_done <= rx_done;
    end
end

endmodule // uart_rx

根据状态寄存器CS,再判断计数寄存器b_cnt的值,在b_cnt等于最大值的一半,即中间时刻进行采样。

至此接收模块完成。

三、测试文件编写,VCS仿真

1.测试使用上篇文章的发送代码,在此基础上再实例化一个接收模块,代码如下

`timescale 1ns/1ps
module usart_tb();

reg nrst;
reg clk;

reg en_tx;
reg [7:0] tx_data;
wire tx_pin,tx_done;
reg [7:0]tx_cnt;

reg en_rx;
wire [7:0] rx_data;
wire rx_done;

//实例化
uart_tx uart_tx(
    .clk(clk),
    .nrst(nrst),
    .tx_en(en_tx),
    .tx_data(tx_data),
    .tx_pin(tx_pin),
    .txdone(tx_done)
);

uart_rx uart_rx(
    .clk(clk),
    .nrst(nrst),
    .rx_en(en_rx),
    .rx_pin(tx_pin),//txpin连接到rxpin
    .rx_data(rx_data),
    .rx_done(rx_done)
);

//产生125Mhz的时钟
initial begin
    clk = 0;
    forever begin
        #4;
        clk = ~clk; 
    end
end

initial begin
    en_tx = 0;
    tx_data = 8'b0;
    tx_cnt = 'd0;
    nrst = 1;
    #100;
    nrst = 0;//复位信号
    #103;
    nrst = 1;
    tx_data = 'd64;
    en_rx = 1;
end

//产生循环发送的信号
always @(negedge clk) begin
    if (nrst) begin
        if (tx_done) begin
            if (en_tx)//刚刚发送完成
                en_tx <= 1'b0;
            else begin//发送完成一段时间
                if (tx_cnt == 'd250) begin
                    tx_cnt <= 'd0;
                    en_tx <= 1'b1;//重启发送
                end
                else
                    tx_cnt <= tx_cnt + 1'b1;
            end
        end
        else begin
            en_tx <= en_tx;
            tx_cnt <= tx_cnt;
        end
    end
end
endmodule // usart_tb

猜你喜欢

转载自blog.csdn.net/a1254484594/article/details/100676080