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

在這裏插入圖片描述

命令行顯示結果

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章