FPGA串口收發(二):串口接收 - 源代碼與仿真測試

FPGA串口收發(二):串口接收 - 源代碼與仿真測試

串行接收數據 1101_1000 ,轉換爲並行數據, 並顯示 D8。

時鐘40MHz,波特率115200

1、源文件

uart_rx.v

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Myminieye
// Engineer: Nill
//
// Create Date:  2020-05-29
// Design Name:
// Module Name:  uart_rx
// Project Name:
// Target Devices:
// Tool Versions:
// Description: 串口接收
// 三段式狀態機,接收8位數據。波特率指定 115200
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
////////////////////////////////////////////////////////////////
/////////////////////
///
`define UD #1

module uart_rx #(
   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 ports
    input clk,
    //input rst_n,             //沒用上
    input uart_rx,        //接收模塊串口接收信號線

    //output ports
    output reg [7:0] rx_data = 8'h00,
    output reg rx_finish = 1'b0     	//串口接收數據有效,接收完成拉高1個BPS
    //output rx_end   				    //接收到停止位,拉高1個clk,沒啥用
);

//==========================================================================
//wire and reg 定義:信號與參數
//==========================================================================

    localparam  [15:0] BPS_NUM_MID = (BPS_NUM + 2'd1) / 2'd2 - 2'd1; //波特率週期的中間點
	
    reg [3:0] rx_cur_st = 0;    //current state of rx state machine.
    reg [3:0] rx_nxt_st = 0;    //next state of rx state machine.

    reg       uart_rx_1d;       //保存uart_rx數據 1個時鐘週期
    reg       uart_rx_2d;       //保存uart_rx 前2個時鐘週期

    wire      rx_start;         //檢測到start信號標誌

    reg [15:0] clk_div_cnt = 0; //波特週期計數,計到一個波特週期
    reg [2:0]  rx_bit_cnt = 0;  //接收數據位bit計數

    reg [7:0] rx_data_reg = 8'h00;      //接收數據緩衝寄存器

    //=================================================
    // FSM Statement:狀態聲明
    // 發送狀態機4個狀態:等待、發送起始位、發送數據、發送結束
    //=================================================
    //one hot with zero idle
    localparam  [3:0] IDLE     = 4'b0000, //空閒狀態,等待開始信號到來
                RECEIVE_START  = 4'b0001, //接收Uart開始信號,低電平一個波特週期.
                RECEIVE_DATA   = 4'b0010, //接收Uart傳輸數據信號,傳輸8bit,每個波特週期中間位置取值,8個週期後跳轉到stop狀態.
                RECEIVE_STOP   = 4'b0100, //停止狀態數據線是高電平,與空閒狀態是一致的,按照協議標準需要等待一個停止位週期再做狀態跳轉.
                RECEIVE_END    = 4'b1000; //結束中轉狀態.

    //==========================================================================
    //logic:邏輯信號初始化與判斷
    //==========================================================================
	
    //雙觸發器(打兩拍2個clk):全局時鐘驅動2級觸發器,將準穩態轉爲穩態
    always @ (posedge clk)
    begin
        uart_rx_1d <= `UD uart_rx;
        uart_rx_2d <= `UD uart_rx_1d;
    end

    //控制信號,接收模塊的狀態: 串口接收信號線從高電平轉爲低電平
    assign rx_start  = ((!uart_rx)&&(uart_rx_1d || uart_rx_2d));//等待兩個clk後,再判斷接收數據,保證數據穩定
	
    //assign rx_end = (rx_cur_st == RECEIVE_END);	//接收到停止位,拉高1個clk,沒啥用

    //時鐘信號,波特週期計數器 division the clock to satisfy baud rate.
    always @ (posedge clk)
    begin
        if( (clk_div_cnt == BPS_NUM) || (rx_cur_st == IDLE))
            clk_div_cnt   <= `UD 16'h0;
        else
            clk_div_cnt   <= `UD clk_div_cnt + 16'h1;
    end

    //在接收數據狀態中,接收的bit位計數,每一個波特週期計數加1
    always @(posedge clk )
    begin
        if(rx_cur_st == IDLE)
            rx_bit_cnt <= `UD 3'h0;
        else if((rx_bit_cnt == 3'h7) && (clk_div_cnt == BPS_NUM))
            rx_bit_cnt <= `UD 3'h0;
        else if ((rx_cur_st == RECEIVE_DATA) && (clk_div_cnt == BPS_NUM))
            rx_bit_cnt <= `UD rx_bit_cnt +3'h1;
        else
            rx_bit_cnt <= `UD rx_bit_cnt;
    end

    //=================================================
    // FSM input:1st always block:sequential state transition
    // 第一段:時序電路 - 現態與次態轉換
    //=================================================
    //state change 狀態跳轉
    always @ (posedge clk)
        rx_cur_st <= rx_nxt_st;   //下一狀態賦給當前狀態

    //=================================================
    // FSM Change: 2nd always block:combinational condition judgment
    // 第二段:組合電路 tate change condition 狀態跳轉條件及規律
    //=================================================
  always @ (*)
    begin
        case(rx_cur_st)
            IDLE        :
            begin
                if(rx_start)    //監測到start信號到來,下一狀態跳轉到start狀態
                    rx_nxt_st = RECEIVE_START;
                else
                    rx_nxt_st = rx_cur_st;//否則,狀態保持不變
            end
            RECEIVE_START  :
            begin               //已完成接收start標誌信號
                if(clk_div_cnt == BPS_NUM)
                    rx_nxt_st = RECEIVE_DATA;
                else
                    rx_nxt_st = rx_cur_st;
            end
            RECEIVE_DATA   :
            begin               //已完成8bit數據的傳輸
                if((rx_bit_cnt == 3'h7) && (clk_div_cnt == BPS_NUM))
                    rx_nxt_st = RECEIVE_STOP;
                else
                    rx_nxt_st = rx_cur_st;
            end
            RECEIVE_STOP   :
            begin               //已完成接收stop標誌信號
                if(clk_div_cnt == BPS_NUM)
                    rx_nxt_st = RECEIVE_END;
                else
                    rx_nxt_st = rx_cur_st;
            end
            RECEIVE_END    :
            begin
                if(!uart_rx_1d)  //數據線重新被拉低,表示新數據傳輸又發送start標誌信號,需要跳轉到start狀態
                    rx_nxt_st = RECEIVE_START;
                else            //沒有其他狀況出現時,回到空閒狀態,等待start信號的到來
                    rx_nxt_st = IDLE;
            end
            default     :   rx_nxt_st = IDLE;
        endcase
    end

    //=================================================
    // FSM Output:3rd always block:the sequential FSM output
    // 第三段:狀態輸出,Moorer,判斷當前狀態Current State
    //=================================================

    //接收數據給輸入:串行轉並行
    always @(posedge clk)
    begin
        case(rx_cur_st)
            IDLE           ,
            RECEIVE_START  :
            begin
                rx_finish <= `UD 1'b0;
                rx_data_reg <= `UD 8'h0;
            end
            RECEIVE_DATA   :
            //-------------方法一、循環右移------------------------
            begin    //在一個波特週期的中間位置,取數據線上傳輸的數據,BPS_NUM=16'd347
                //if( clk_div_cnt == BPS_NUM[15:1])
                if( clk_div_cnt == BPS_NUM_MID ) //波特率週期的中間點
                    rx_data_reg <= `UD {uart_rx , rx_data_reg[7:1]};
            end  //uart_rx數據輸入,每次傳給緩衝寄存器的最高位,構建新的8位數據
                 //循環右移,Uart傳輸低位在前,最後一個bit剛好是最高位

            //------------方法二、計數到相應位,直接賦值------------
            /*
                begin
                case(rx_bit_cnt)
                    3'h0  :  rx_data_reg [0]  <= `UD uart_rx ;
                    3'h1  :  rx_data_reg [1]  <= `UD uart_rx ;
                    3'h2  :  rx_data_reg [2]  <= `UD uart_rx ;
                    3'h3  :  rx_data_reg [3]  <= `UD uart_rx ;
                    3'h4  :  rx_data_reg [4]  <= `UD uart_rx ;
                    3'h5  :  rx_data_reg [5]  <= `UD uart_rx ;
                    3'h6  :  rx_data_reg [6]  <= `UD uart_rx ;
                    3'h7  :  rx_data_reg [7]  <= `UD uart_rx ;
                    default: rx_data_reg      <= `UD rx_data_reg ;//默認接收,保持不變
                end
             */

            RECEIVE_STOP   :
            begin
                rx_finish <= `UD 1'b1;  //串口接收數據有效,接收完成拉高1個BPS
                rx_data <= `UD rx_data_reg;
                //將緩衝寄存器的值賦值給輸出寄存器,內部並行,可以一次性傳8位
            end
            RECEIVE_END    : rx_data_reg <= `UD 8'h0;
            default        : rx_finish <= `UD 1'b0;
        endcase
    end

endmodule

2、仿真文件testbench

uart_rx_tb.v

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Myminieye
// Engineer: Nill
//
// Create Date:
// Design Name:
// Module Name:
// Project Name:
// Target Devices:
// Tool Versions:
// Description: 測試串口接收
// 模擬串口信號線,串行接收數據 1101_1000 ,轉換爲並行數據, 並顯示 D8
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
`define UD #1

module tb_uart_rx();

//==========================================================================
//wire and reg 定義:信號與參數
//==========================================================================
	// input to module
	reg       sim_clk;		//模擬時鐘信號
	//reg     tx_pulse;     // active posedge
	reg 	  sim_rst_n;
	reg       uart_rx;		//串口發送信號線

	//output from module
	wire [7:0] rx_data;		//送入串口發送模塊,準備發送的數據
	wire       rx_en;       //串口接收數據有效,接收完成拉高1個BPS
	wire	   rx_finish; 	//接收完成標誌,拉高1個clk
	
	//時鐘參數
	parameter SYS_CLK_FRE = 40_000_000;     //系統頻率40MHz  40_000_000
	parameter SYS_CLK_PERIOD = 1_000_000_000/SYS_CLK_FRE;  //週期25ns
	parameter RST_CYCLE = 5;                //復位持續時間,clk時鐘週期數
	parameter RST_TIME = RST_CYCLE * SYS_CLK_PERIOD;    //復位時間:5個時鐘週期

	//波特率參數
  	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
		//模擬復位信號:一次,低電平5個clk
		#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;      //解除復位

		//==========================================================================
		//模擬串口接收:串行信號輸入,轉化成並行數據,並顯示
		//==========================================================================			
			uart_rx = 1'b1;	   //串口發送線,默認拉高
		repeat( BPS_NUM*1 ) @(posedge sim_clk);	    //循環347個時鐘週期,即一個波特率週期
		//#BAUD_RATE_PERIOD; 						//直接延時,一個波特率週期
		
		$display("Initialization complete. BAUD_RATE is %d",BAUD_RATE); //命令行顯示初始化完成,輸出BPS_NUM
		
		//發送起始位
			uart_rx = 1'b0;	
		#BAUD_RATE_PERIOD;					

		//串行數據,一位一位送入接收信號線:***從位0到位7***,依次發送
		//測試數據爲8'hD8=8'b1101_1000
			uart_rx = 1'b0;		
		#BAUD_RATE_PERIOD; 
			uart_rx = 1'b0;		
		#BAUD_RATE_PERIOD; 
			uart_rx = 1'b0;		
		#BAUD_RATE_PERIOD; 		
			uart_rx = 1'b1;		
		#BAUD_RATE_PERIOD; 
			uart_rx = 1'b1;		
		#BAUD_RATE_PERIOD; 
			uart_rx = 1'b0;		
		#BAUD_RATE_PERIOD; 
			uart_rx = 1'b1;		
		#BAUD_RATE_PERIOD; 
			uart_rx = 1'b1;		
		#BAUD_RATE_PERIOD; 		
		$display("The uart_rx 8'hD8 = 8'b1101_1000 has been sent.");  //命令行顯示:串口信號線數據已發送

		//串口:結束位
			uart_rx = 1'b1;	
		#BAUD_RATE_PERIOD;
			$display("The uart_rx has received. rx_data = 8'h%h",rx_data);  
		//命令行顯示:串口信號線接收已結束,顯示接收到的數據

		repeat( BPS_NUM*5 ) @(posedge sim_clk);
			$stop;		//結束仿真
		
	end

//==========================================================================
//調用top模塊
//==========================================================================
    //串口發送
    uart_rx #(
         //.CLK_SYS   (  SYS_CLK_FRE), //系統時鐘
         .BPS_NUM (  BPS_NUM  )  // 時鐘/波特率,1 bit位寬所需時鐘週期的個數
     )
     u_uart_rx(
        .clk      (  sim_clk  ),// input       clk,
		.rstn	  (  sim_rstn ),// input 
		.uart_rx  (  uart_rx  ),// input reg  串口接收信號線
		
        .rx_data  (  rx_data  ),// output  接收到的數據
        .rx_en    (  rx_en    ),// output  串口接收數據有效,接收完成拉高1個BPS
        .rx_finish(  rx_finish) // output  接收完成標誌,拉高1個clk
    );

endmodule

3、仿真結果

Modelsim波形
在這裏插入圖片描述

移位結果

rx_data_reg <= `UD {uart_rx , rx_data_reg[7:1]};

在這裏插入圖片描述

命令行顯示
在這裏插入圖片描述

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