串口(UART)发送模块-verilog实现-VCS仿真

一、要点

1.结构:TTL电平的串口由两条信号线连接,一条为接收端RX,另一条为发送端TX。UART是一种无时钟线的串行接口,接线简单。

2.时序:即信息流动的时间顺序。下图是串口的时序图。

       串口数据按帧发送,帧内的数据位、检验位、停止位是可配置的,一般情况下,数据位为8位,无检验位,停止位为1位,一帧数据则包含1+8+0+1=10位(bit)。

      空闲状态下,总线为高电平,起始位固定为低电平,当在空闲状态下发送低电平时代表一帧数据开始发送,停止位固定为高电平,代表一帧数据结束。

3.波特率:数据传输的速率,即每秒中能传输的bit的个数。例如波特率115200即每秒钟最多能传输115200个bit的数据(包含起始位、停止位、校验位),则每bit数据需要维持的时间为1s/115200≈8.68055us。

二、实现

1.架构:发送模块主要包含3个部分,1、用计数器实现波特率定时,2、模块状态的转移,3、发送数据的填充

2.Verilog语言描述

新建一个uart_tx.v文件

module uart_tx
(
    input clk,
    input nrst,
    input tx_en,
    input [7:0]tx_data,
    output tx_pin,
    output tx_done
);

以上描述模块的端口,分别为时钟、复位、使能、发送数据、发送引脚、发送完成标志。

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

reg [3:0]CS;    //状态
reg [10:0]b_cnt; //用来产生波特率时钟
reg [10:0]tx_buf;//包含起始位与停止位的数据缓冲区

reg r_tx_pin;
reg r_txdone;

以上定义模块的参数及本地参数、使用到的寄存器。模块默认输入时钟为125MHz,波特率为115200。

//CS依据波特率计数器自增,实现状态转移
task CS_increase;
    if(CS == 'd10) begin    //一帧完成,复位
        CS <= 'd0;
        b_cnt <= 'd0;
    end
    else if (b_cnt == B_CNT_MAX) begin//计数满1bti的发送时间,转移到下一个bit,计数器清零
        CS <= CS + 1'b1;
        b_cnt <= 'd0;
    end
    else //计数中
        b_cnt <= b_cnt + 1'b1;
endtask

以上定义一个任务,在其他代码中被调用,实现状态寄存器CS根据波特率计数器自增,实现状态转移。此段结合了波特率定时和状态转移两部分。

always @(posedge clk) begin
    if (!nrst) begin//复位
        CS <= 'd0;
        b_cnt <= 'd0;
        tx_buf <= 'd0;
        r_tx_pin <= 1'b1;
        r_txdone <= 1'b1;
    end
    else if (tx_en) begin
        //状态转移
        CS_increase;

        //各状态输出
        if (CS == 'd0) begin
            r_txdone <= 1'b0;//开始发送,发送完成标志置0
            tx_buf <= {2'b11,tx_data,1'b0};
        end
        else if (CS == 'd10) begin
            r_txdone <= 1'b1;//发送完成标志置1
            tx_buf <= tx_buf;
        end
        else begin           //正在发送数据位、停止位
            r_txdone <= 1'b0;
            tx_buf <= tx_buf;
        end
    
        r_tx_pin <= tx_buf[CS];//输出引脚数据更新
    end
    else
        r_tx_pin <= 1'b1;
end

 以上部分是发送模块的主要结构,在时钟上升沿到来后检测有无复位(nrst=0时进行复位),无复位任务且发送模块被使能时则完成两个任务:1、调用状态转移任务CS_increase实现状态转移,2、根据状态寄存器CS对发送寄存器r_tx_pin、发送完成寄存器r_txdone进行赋值。

状态寄存器CS的每个值对应TX线上的每个发送状态,CS=0时表示开始发送起始位,此时将需要发送的数据填充进tx_buf,1'b0是起始位,tx_data是数据,2'b11是一位停止位加一个结束状态时发送的数据(或者说是停止位加空闲状态)。

CS=10表示停止位发送完后的下一个状态,此时tx线进入空闲状态,与停止位一样发送1。此时将发送完成标志r_txdone置1。(在后续的顶层文件中,当检测到发送完成标志置1时会失能tx_en,使得状态寄存器CS无法进行转移,实现的效果是r_tx_pin <= 1'b1,即tx线固定输出1)。

//驱动输出声明
assign tx_pin = r_tx_pin;
assign tx_done = r_txdone;

endmodule // uart_tx

最后将输出寄存器驱动到输出的线网,至此发送模块定义完成。

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

1.编写测试文件:包括产生时钟、管理发送使能信号

新建一个uart_tb.v文件

`timescale 1ns/1ps

module uart_tb();
reg nrst;
reg clk;

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

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

定义一个uart_tb模块用于测试,定义相关信号,实例化uart_tx模块

//产生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;
end

产生125MHz的时钟,初始化各信号的值

//产生循环发送的信号
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

在tx_done置高的时候说明发送完成,将en_tx失能;然后使用tx_cnt进行计数,一段时间后重新使能en_tx,开始新一帧的发送

2.VCS仿真

将uart_tx.v和uart_tb.v文件放在同一文件夹中,在该目录路径中打开终端,使用vcs -full64 *.v -debug_all命令进行编译

再使用./simv -gui命令打开gui图形界面,将相关信号添加到示波器中进行仿真 

仿真波形如图,在红线处tx_done信号上升、en_tx信号下降,经过计数器一段时间的计数后en_tx再次置高

猜你喜欢

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