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波形
FPGA串口收发(二):串口接收 - 源代码与仿真测试_第1张图片

移位结果

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

FPGA串口收发(二):串口接收 - 源代码与仿真测试_第2张图片

命令行显示
FPGA串口收发(二):串口接收 - 源代码与仿真测试_第3张图片

你可能感兴趣的:(FPGA)