FPGA串口收发(一):串口发送 - 源代码与仿真测试

串口发送的数据:0 -> A (1010) -> C (1100) ->0
时钟40MHz,波特率115200

1、源文件

uart_tx.v
`timescale 1ns / 1ps
//
// Company: Myminieye
// Engineer: Nill
//
// Create Date:  2020-05-29
// Design Name:
// Module Name:  uart_tx
// Project Name:
// Target Devices:
// Tool Versions:
// Description: 串口发送
// 三段式状态机,发送8位数据。波特率指定 115200
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
/
`define UD #1
module uart_tx #(
   parameter   BPS_NUM =  16'd347  // 时钟/波特率,用时钟周期构造波特率周期
    //  BPS_NUM = 40_000_000/115200 = 347.22 = 16'd347
    //  1 bit位宽所需时钟周期的个数。最长的波特率计数,10417,二进制有14位,取16位
    //  parameter BPS_4800: 40MHz set 8333 ; 50MHz set 10417  
    //  parameter BPS_9600: 40MHz set 4167 ; 50MHz set 5208  
    //  parameter BPS_115200: 40MHz set 347; 50MHz set 434    
)
(
    input clk,              //clock
    input [7:0]    tx_data, //等待发送的字节数据 uart tx data signal byte;
    

    input tx_pulse,         //外部传入,触发串口发送
    //input rx_finish,    //接收应答,表示接收完成(自发自收)
    output reg uart_tx,   // 发送模块串口发送信号线 uart tx transmit data line
    output tx_busy,        // 发送模块忙状态指示 uart tx module work states,high is busy;
    
    //仿真信号
    output reg tx_finish = 1'b0 //串口发送数据结束标志,8位数据发完,拉高一个BPS

);
//==========================================================================
//wire and reg 定义:信号与参数
//==========================================================================
    

    //parameter BPS_NUM_MID  = (BPS_NUM + 2'd1) / 2'd2 - 2'd1; //波特率周期的中间点
    reg [3:0] tx_cur_st    = 0;         //发送 当前状态
    reg [3:0] tx_nxt_st    = 0;         //发送 下一状态
    reg       tx_pulse_reg = 0;         //发送触发信号缓存
    reg [3:0] pulse_delay_cnt = 0;      //触发延时
    reg       tx_en = 0;                //开启串口发送,整个发送周期都拉高
    //data_gen模块 ,产生数据后,就开启串口发送  (write_en)->tx_en
    reg [2:0] tx_bit_cnt   = 0;         //发送位数,计数
    reg [15:0]clk_div_cnt  = 0;         //波特周期计数,计到一个波特周期
    reg [7:0] tx_data_reg  = 0;         //发送数据缓冲寄存器

//=================================================
// FSM Statement:状态声明
// 发送状态机4个状态:等待、发送起始位、发送数据、发送结束
//=================================================
    //one hot with zero idle
    localparam  [3:0] IDLE  = 4'b0000,  //tx state machine's state.空闲状态
                SEND_START  = 4'b0001,  //tx state machine's state.发送start状态
                SEND_DATA   = 4'b0010,  //tx state machine's state.发送数据状态
                SEND_STOP   = 4'b0100,  //tx state machine's state.发送stop状态
                SEND_END    = 4'b1000;  //tx state machine's state.发送结束状态

//==========================================================================
//logic:逻辑信号初始化与判断
//==========================================================================
    //发送模块:忙碌状态
    assign tx_busy = (tx_cur_st != IDLE);//非空闲,发送态忙
    //tx_cur_st = SEND_XXX,正在发送,忙状态
    //tx_cur_st = IDLE, 发送结束了/空闲状态,不忙
    //发送模块这8位发完了,传出tx_busy不忙,进入data_gen,又返回发送模块,tx_pulse触发下一组8位发送
    //触发串口发送 tx_pulse  0->1
    //打一拍,D触发器,构造上升沿触发
    always @ (posedge clk)
    begin
        tx_pulse_reg <= `UD tx_pulse;
    end

    //开启串口发送状态 tx_en:整个发送周期都拉高
    always @(posedge clk )
    begin
    // rstn复位 tx_en=0,在 data_gen中实现
        if(~tx_pulse_reg & tx_pulse)   //tx_pulse 上升沿触发
            tx_en <= 1'b1;
        else if(tx_cur_st == SEND_END) //串口发送结束,关闭串口发送
            tx_en <= 1'b0;
    end
    
    //时钟信号,波特周期计数器(时钟分频)
    always @ (posedge clk)
    begin   //发送触发信号,且打了一拍,时间也计到了一个波特周期
        if( (clk_div_cnt == BPS_NUM) || (~tx_pulse_reg & tx_pulse))
            clk_div_cnt   <= `UD 16'h0;
            //clk_out <= 16'h1;
        else
            clk_div_cnt   <= `UD clk_div_cnt + 16'h1;
            //clk_out <= 16'h0;
    end
    
    //发送数据状态中,发送bit位计数(8),以波特周期累加 count the number has transmited
    always @(posedge clk )
    begin
        if(!tx_en)
            tx_bit_cnt <= `UD 3'h0;
        else if((tx_bit_cnt == 3'h7) && (clk_div_cnt == BPS_NUM))
            tx_bit_cnt <= `UD 3'h0;
        else if ((tx_cur_st == SEND_DATA) && (clk_div_cnt == BPS_NUM))
            tx_bit_cnt <= `UD tx_bit_cnt +3'h1;
        else
            tx_bit_cnt <= `UD tx_bit_cnt;
    end
    
    //触发信号延时:触发条件进来,延时15个clk (4'hf)
    always @(posedge clk)
    begin
        if (tx_cur_st != IDLE)
            pulse_delay_cnt <= `UD 4'hf;
        else
            pulse_delay_cnt <= pulse_delay_cnt + 4'h1;
    end

//=================================================
// FSM input:1st always block:sequential state transition
// 第一段:时序电路 - 现态与次态转换
//=================================================
    //state change 状态跳转
    always @ (posedge clk)
        tx_cur_st <= tx_nxt_st;   //下一状态赋给当前状态
//=================================================
// FSM Change: 2nd always block:combinational condition judgment
// 第二段:组合电路 tate change condition 状态跳转条件及规律
//=================================================
  always @ (*)
    begin
        case(tx_cur_st)
            IDLE:      //4'b0000
            begin     //触发发送,15个时钟周期延时,再转到发送start状态
                if( (tx_en) & (pulse_delay_cnt == 4'hf) )
                    tx_nxt_st = SEND_START;//接收完成,可以开始发送数据
                else
                    tx_nxt_st = tx_cur_st;//否则,状态保持不变
            end
            SEND_START:    //4'b0001
            begin //发送一个波特周期的低电平后进入,发送数据状态
                if(clk_div_cnt == BPS_NUM)
                    tx_nxt_st = SEND_DATA;
                else
                    tx_nxt_st = tx_cur_st;
            end
            SEND_DATA:     //4'b0010
            begin 
                if((tx_bit_cnt == 3'h7) && (clk_div_cnt == BPS_NUM))
                    tx_nxt_st = SEND_STOP;
                else
                    tx_nxt_st = tx_cur_st;
            end
            SEND_STOP:     //4'b0100
            begin //计时8个波特周期后(发送了8bit数据),跳转到发送stop状态
                if(clk_div_cnt == BPS_NUM)
                    tx_nxt_st = SEND_END;
                else
                    tx_nxt_st = tx_cur_st;
            end
            SEND_END:      //4'b1000
                tx_nxt_st = IDLE;   //变化太快,1个clk,而不是1个BPS就跳转了
            default:    tx_nxt_st = IDLE;
        endcase
    end

//=================================================
// FSM Output:3rd always block:the sequential FSM output
// 第三段:状态输出,Moorer,判断当前状态Current State
//=================================================
    //发送数据给输出:并行转串行
    always @(posedge clk) begin
        if(tx_en) begin
            case(tx_cur_st)
                IDLE        : uart_tx <= `UD 1'h1;
                SEND_START  : begin
                    uart_tx   <= `UD 1'h0;  //发送数据,先把串口线拉低
                    tx_finish <= `UD 1'b0;  //串口发送数据结束标志,先拉低
                end
                SEND_DATA   : begin
                /* //-------------方法一、循环右移------------------------
                        tx_data_reg[6:0]  <= `UD tx_data_reg[7:1];
                        //已经有了完整8位数据,循环右移,高位依次移到低位/最低位0
                        uart_tx      <= `UD tx_data_reg[0];
                        //uart_tx数据输出, 每次把缓冲寄存器的最低位0传出去
                         //同一个clk触发 数据发送与移位,每次发送的是上一次的移位结果
                 */
                //------------方法二、计数到相应位,直接赋值------------
                    case(tx_bit_cnt)
                        3'h0    : uart_tx <= `UD tx_data[0];
                        3'h1    : uart_tx <= `UD tx_data[1];
                        3'h2    : uart_tx <= `UD tx_data[2];
                        3'h3    : uart_tx <= `UD tx_data[3];
                        3'h4    : uart_tx <= `UD tx_data[4];
                        3'h5    : uart_tx <= `UD tx_data[5];
                        3'h6    : uart_tx <= `UD tx_data[6];
                        3'h7    : uart_tx <= `UD tx_data[7];
                        default : uart_tx <= `UD 1'h1;
                    endcase
                end
                SEND_STOP   : begin
                    uart_tx <= `UD 1'h1;  //发送停止状态 输出1个波特周期高电平
                    //每次输出,uart_tx串行只传一位,不能直接把tx_data_reg 并行输出
                    tx_finish <= `UD 1'b1;  //串口发送数据结束标志,拉高
                end
                default     : begin
                    uart_tx <= `UD 1'h1; // 其他状态默认与空闲状态一致,保持高电平输出
                    tx_finish <= `UD 1'b0;
                end
            endcase
            //case(tx_cur_st)
        end
        else
            uart_tx <= `UD 1'h1;
    end
endmodule
//module uart_tx

2、仿真文件testbench

uart_tx_tb.v

`timescale 1ns / 1ps
//
// Company: Myminieye
// Engineer: Nill
//
// Create Date:
// Design Name:
// Module Name:
// Project Name:
// Target Devices:
// Tool Versions:
// Description: 测试串口发送
// 串口发送的数据由内部提供:0 -> A (00001010) -> C (00001100)
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
`define UD #1
module tb_uart_tx();

//==========================================================================
//wire and reg 定义:信号与参数
//==========================================================================
    reg       sim_clk;      //模拟时钟信号
    reg       sim_rst_n;
    reg       tx_pulse;     // active posedge
    

    reg [7:0] tx_data;      //送入串口发送模块,准备发送的数据
    wire      uart_tx;      //串口发送信号线
    wire      tx_busy;      //串口发送模块状态
    //时钟参数
    parameter SYS_CLK_FRE = 40_000_000;     //系统频率40MHz  40_000_000
    parameter SYS_CLK_PERIOD = 1_000_000_000/SYS_CLK_FRE;  //周期25ns
    parameter BAUD_RATE = 115200;   //串口波特率
    parameter BAUD_RATE_PERIOD  = 1_000_000_000/BAUD_RATE;
    //波特率周期,0.104ms = 104us,1/9600 s = 1^9 /9600 ns = 4167 sim_clk
    //波特率周期, 1/115200 = 8680 ns  =  8.7us = 347 sim_clk
    parameter [15:0]  BPS_NUM = SYS_CLK_FRE / BAUD_RATE; //时钟/波特率,用时钟周期构造波特率周期
    //  BPS_NUM = 40_000_000/115200 = 347.22 = 16'd347
    //  1 bit位宽所需时钟周期的个数。最长的波特率计数,10417,二进制有14位,取16位
    //  parameter BPS_4800: 40MHz set 8333 ; 50MHz set 10417
    //  parameter BPS_9600: 40MHz set 4167 ; 50MHz set 5208
    //  parameter BPS_115200: 40MHz set 347; 50MHz set 434

//==========================================================================
//模拟:信号的输入,显示输出结果
//==========================================================================
    //模拟系统时钟:40MHz,25ns
    always #((SYS_CLK_PERIOD+1)/2-1) sim_clk = ~sim_clk; //延时,电平翻转
    initial begin
        //模拟复位信号:拉低一次
        #0;
            sim_clk = 1'b0;
            sim_rst_n = 1'b0;      //复位拉低,有效,
        //#RST_TIME;               //延时:保持足够长时间(至少5个clk)
        #BAUD_RATE_PERIOD;         //5个clk时间轴太短,仿真改为1个BPS,更明显
            sim_rst_n = 1'b1;      //解除复位       
        //==========================================================================
        //模拟串口发送:并行数据,串行输出
        //==========================================================================    
        

        tx_pulse = 0;
        tx_data = 8'd0; 
        repeat( BPS_NUM*1 ) @(posedge sim_clk);   //循环347个时钟周期,即一个波特率周期
        //#BAUD_RATE_PERIOD;        //直接延时,一个波特率周期      
        
        $display("Initialization complete. BAUD_RATE is %d",BAUD_RATE); //命令行显示初始化完成,输出BAUD_RATE
        //传递 第一组数据:8位并行数据,一次性送入串口发送模块
        tx_data = 8'hA;             //00001010
        #BAUD_RATE_PERIOD;
        
        //开启触发信号:串口发送
        tx_pulse = 1;               //高有效
        #BAUD_RATE_PERIOD;
        
        //结束触发:串口发送
        tx_pulse = 0;
        #BAUD_RATE_PERIOD;
        $display("The first tx_data  A (1010) has been sent.");  //命令行显示:第1组数据发送完
        //延时 12个 BPS 波特率周期,再发第二组数据
        repeat( BPS_NUM*12 ) @(posedge sim_clk);
        //传递 第二组数据:8位并行数据,一次性送入串口发送模块
        tx_data = 8'hC; //00001100
        #BAUD_RATE_PERIOD;
        
        //开启触发信号:串口发送
        tx_pulse = 1;
        #BAUD_RATE_PERIOD;
        
        //结束触发:串口发送
        tx_pulse = 0;
        #BAUD_RATE_PERIOD;
        $display("The second tx_data C (1100) has been sent.");   //命令行显示:第2组数据发送完
        
        repeat( BPS_NUM*5 ) @(posedge sim_clk);
            $stop;      //结束仿真
    end

//==========================================================================
//调用top模块
//==========================================================================
    //串口发送
    uart_tx #(
         //.CLK_SYS   (  SYS_CLK_FRE), //系统时钟
         .BPS_NUM (  BPS_NUM  )  // 时钟/波特率,用时钟周期构造波特率周期
     )
     u_uart_tx(
        .clk      (  sim_clk      ),// input       clk,
        .tx_data  (  tx_data  ),// input [7:0] tx_data,
        .tx_pulse (  tx_pulse   ),// input       外部输入,开始产生数据->开启串口发送状态
        .uart_tx  (  uart_tx  ),// output reg  uart_tx,
        .tx_busy  (  tx_busy  ) // output      输出,串口接收忙状态
    );

endmodule

3、ModelSim仿真结果

第一组数据 8’hA -> 00001010

在这里插入图片描述

第二组数据 8’Hc -> 00001100

在这里插入图片描述

命令行显示结果

在这里插入图片描述

  • 7
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值