UART的FPGA状态机实现

目录

  • 发送模块
  • 接收模块
  • 测试:实现数据回环

发送模块

  • 划分为5个状态:空闲状态、发送起始位、发送数据位、发送校验位、发送停止位。
  • 通过检测上升沿,检测发送模块是否被使能。需要注意的是上升沿检测的两个寄存器uart_en_cur、uart_en_pre的初始值问题。如果两个寄存器初始值为0,当uart_en初始信号为1时,就会错误的检测到一个上升沿信号。
  • 注意三段式状态机里面,状态转移的判断里面尽量不要出现其他变量的赋值,只是判断状态的转移。先前有过在里面对data_cnt(data_cnt = data_cnt + 1)进行赋值,但是被综合成为了一个锁存器。而锁存器对于毛刺比较敏感,组合逻辑电路中可能存在竞争冒险,产生了毛刺,所以data_cnt的值不受控制。之后将状态切换的输出单独写在一个always模块中(时序逻辑)。
  • uart的时钟通过对系统时钟的计数实现。
  • 状态输出和状态切换的处理其实可以放在一个always模块里面。
  • 实现代码
module uart_send(
	
	input        sys_clk,
	input        rst_n,
	input        uart_en,   //发送使能
	input  [7:0] uart_din,  //待发送数据
	
	output       uart_status,//模块状态,0表示空闲
    output reg	 uart_txd    //发送端口
	
);


localparam VERIFY_NONE	  = 0;          //无校验
localparam VERIFY_ODD 	  = 1;          //奇校验
localparam VERIFY_EVEN	  = 2;          //偶校验

localparam STATUS_IDLE    = 3'b000;//空闲状态
localparam STATUS_START   = 3'b100;//发送起始位
localparam STATUS_DATA    = 3'b101;//发送数据位
localparam STATUS_VERIFY  = 3'b110;//发送校验位
localparam STATUS_STOP    = 3'b111;//发送停止位

parameter SYS_FREQ 	      = 50_000_000;//系统时钟频率
parameter UART_BAUDRATE   = 115200;    //uart波特率
parameter PERIOD_CNT      = SYS_FREQ/UART_BAUDRATE;//一个周期计数

parameter DATA_SIZE       = 8;              //数据位长度
parameter STOP_SIZE       = 1;              //停止位长度
parameter VERIFY_SIZE     = 0;              //校验位长度
parameter VERIFY_METHOD   = VERIFY_NONE;    //校验方式


reg       uart_en_cur;
reg       uart_en_pre;
reg       uart_verify_bit; //校验位
reg [7:0] uart_data;       //寄存发送的数据
reg [2:0] present_status;  //当前模块所处状态
reg [2:0] next_status;
reg [2:0] data_cnt;        //数据计数器
reg [1:0] stop_cnt;        //停止位计数器
reg [15:0]clk_cnt;         //时钟计数

//用于检测上升沿使能信号
always @(posedge sys_clk or negedge rst_n)begin

	if(!rst_n)begin
		uart_en_cur <= 1'b1;//注意初始值的设置
		uart_en_pre <= 1'b1;
	end
	else begin
		uart_en_cur <= uart_en;
		uart_en_pre <= uart_en_cur;		
	end

end

//模块状态输出
assign uart_status = (present_status == STATUS_IDLE)?1'b0:1'b1;

//为发送过程提供时钟
always @(posedge sys_clk or negedge rst_n)begin
	
	if(!rst_n)begin
		clk_cnt <= 16'b0;
	end
	else if(present_status != STATUS_IDLE)begin
		if(clk_cnt < PERIOD_CNT)
			clk_cnt <= clk_cnt + 16'b1;
		else
			clk_cnt <= 16'b0;
	end
	else
		clk_cnt <= 16'b0;
		
end


//状态转移
always @(posedge sys_clk or negedge rst_n)begin

	if(!rst_n)
		present_status <= STATUS_IDLE;
	else
		present_status <= next_status;
		
end


//判断转移条件
always @(*) begin
	
	case(present_status)
		STATUS_IDLE://空闲状态
		begin
		if((~uart_en_pre) & uart_en_cur)//模块被使能(上升沿)			
				next_status = STATUS_START; //状态转移
			else
				next_status = STATUS_IDLE;		
		end
		STATUS_START:
		begin
			if(clk_cnt == PERIOD_CNT)//维持一个周期的起始位
				next_status = STATUS_DATA;
			else
				next_status = STATUS_START;
		end
		STATUS_DATA://发送数据
		begin
			if(clk_cnt == PERIOD_CNT && data_cnt == DATA_SIZE - 1)begin
				if(VERIFY_METHOD == VERIFY_NONE)
					next_status = STATUS_STOP;
				else
					next_status = STATUS_VERIFY;
			end
			else 
				next_status = STATUS_DATA;
		end
		STATUS_VERIFY://发送校验位状态
		begin
			if(clk_cnt == PERIOD_CNT)
				next_status = STATUS_STOP;
			else 
				next_status = STATUS_VERIFY;
		end
		STATUS_STOP:
		begin
			if(clk_cnt == (PERIOD_CNT >> 1) && stop_cnt == STOP_SIZE - 1)
				next_status = STATUS_IDLE;		
			else 
				next_status = STATUS_STOP;
		end
		default:next_status = STATUS_IDLE;
	endcase

end

//状态切换处理
always @(posedge sys_clk or negedge rst_n)begin
	
	if(!rst_n)begin
		data_cnt <= 3'b0;
		stop_cnt <= 2'b0;
	end	
	else begin
		case(present_status)
			STATUS_IDLE:
			begin
				if(next_status == STATUS_START)//寄存数据
					uart_data <= uart_din;
			end
			STATUS_START:
			begin
				if(next_status == STATUS_DATA)begin
					data_cnt <= 3'b0;//清计数器
					stop_cnt <= 2'b0;
					if(VERIFY_METHOD == VERIFY_EVEN)//校验
						uart_verify_bit = ^uart_data;
					else if(VERIFY_METHOD == VERIFY_ODD)
						uart_verify_bit = ~(^uart_data);
				end
			end
			STATUS_DATA:
			begin
				if(clk_cnt == PERIOD_CNT && data_cnt < DATA_SIZE - 1)
					data_cnt <= data_cnt + 3'b1;				
			end
			STATUS_STOP:
			begin
				if(clk_cnt == PERIOD_CNT && stop_cnt < STOP_SIZE - 1)
					stop_cnt <= stop_cnt + 2'b1;
			end
			default:;
		endcase 
	end

end


//状态输出
always @(posedge sys_clk or negedge rst_n)begin
	
	if(!rst_n)
		uart_txd <= 1'b1;
	else begin
		case(present_status)
			STATUS_START:uart_txd <= 1'b0;
			STATUS_DATA:uart_txd <= uart_data[data_cnt];	
			STATUS_VERIFY:uart_txd <= uart_verify_bit;
			STATUS_STOP:uart_txd <= 1'b1;
			default:uart_txd <= 1'b1;
		endcase 
	end
		
end

endmodule

接收模块

  • 接收模块的实现思路和发送模块基本一致。
  • 实现代码
module uart_recv(
	
	input	       		sys_clk,
	input	       		rst_n,
	input	       		uart_rxd,    //接收端口
	
	output  reg         uart_done,   //数据更新,输出一个单脉冲
	output	reg  [7:0]	uart_dout    //数据输出
	
);


localparam VERIFY_NONE    = 0;            //无校验
localparam VERIFY_ODD     = 1;            //奇校验
localparam VERIFY_EVEN    = 2;            //偶校验

localparam STATUS_IDLE    = 3'b000;	      //空闲状态
localparam STATUS_START   = 3'b100;       //接收起始位
localparam STATUS_DATA    = 3'b101;       //接收数据位
localparam STATUS_VERIFY  = 3'b110;       //接收校验位
localparam STATUS_STOP    = 3'b111;       //停止位

parameter SYS_FREQ 	      = 50_000_000;   //系统时钟频率
parameter UART_BAUDRATE   = 115200;       //uart波特率
parameter PERIOD_CNT      = SYS_FREQ/UART_BAUDRATE;//一个周期计数

parameter DATA_SIZE    	  = 8;              //数据位长度
parameter STOP_SIZE    	  = 1;              //停止位长度
parameter VERIFY_SIZE  	  = 0;              //校验位长度
parameter VERIFY_METHOD	  = VERIFY_NONE;    //校验方式


reg	uart_rxd_cur;
reg uart_rxd_pre;

reg [2:0] present_status;/*synthesis noprune*/
reg [2:0] next_status;

reg [2:0] data_cnt;        //数据计数器
reg [1:0] stop_cnt;        //停止位计数器
reg [15:0]clk_cnt;         //时钟计数


//用于检测起始信号,即一个下降沿
always@(posedge sys_clk or negedge rst_n)begin
	
	if(!rst_n)begin
		uart_rxd_cur <= 1'b0;
		uart_rxd_pre <= 1'b0;
	end
	else begin
		uart_rxd_cur <= uart_rxd;
		uart_rxd_pre <= uart_rxd_cur;
	end

end


//为发送过程提供时钟
always @(posedge sys_clk or negedge rst_n)begin
	
	if(!rst_n)begin
		clk_cnt <= 16'b0;
	end
	else if(present_status != STATUS_IDLE)begin
		if(clk_cnt < PERIOD_CNT)
			clk_cnt <= clk_cnt + 16'b1;
		else
			clk_cnt <= 16'b0;
	end
	else
		clk_cnt <= 16'b0;
		
end


//状态转移
always @(posedge sys_clk or negedge rst_n)begin

	if(!rst_n)
		present_status <= STATUS_IDLE;
	else
		present_status <= next_status;
		
end

//判断转移条件
always @(*) begin
	
	case(present_status)
		STATUS_IDLE://空闲状态
		begin
			if((~uart_rxd_cur) & uart_rxd_pre)
				next_status = STATUS_START;
			else
				next_status = STATUS_IDLE;
		end
		STATUS_START:
		begin
			if(clk_cnt == (PERIOD_CNT>>1))begin 
				if(uart_rxd == 1'b0)
					next_status = STATUS_DATA;
				else
					next_status = STATUS_IDLE;
			end
			else
				next_status = STATUS_START;	
		end
		STATUS_DATA:
		begin
			if(clk_cnt == (PERIOD_CNT>>1) && data_cnt == DATA_SIZE-1)begin
				if(VERIFY_METHOD == VERIFY_NONE)
					next_status = STATUS_STOP;
				else
					next_status = STATUS_VERIFY;
			end
			else
				next_status = STATUS_DATA;
		end
		STATUS_VERIFY://接收校验位状态
		begin
			if(clk_cnt == (PERIOD_CNT>>1))
				next_status = STATUS_STOP;
			else
				next_status = STATUS_VERIFY;
		end
		STATUS_STOP:
		begin
			if(clk_cnt == (PERIOD_CNT>>1) && stop_cnt == STOP_SIZE-1)
				next_status = STATUS_IDLE;
			else
				next_status = STATUS_STOP;
		end
		default:next_status = STATUS_IDLE;
	endcase

end

//状态输出
always @(posedge sys_clk or negedge rst_n)begin
	
	if(!rst_n)begin
		data_cnt <= 3'b0;
		stop_cnt <= 2'b0;
		uart_done <= 1'b0;
	end	
	else begin
		case(present_status)
			STATUS_IDLE:uart_done <= 1'b0;
			STATUS_START:
			begin
				if(next_status == STATUS_DATA)begin
					data_cnt <= 3'b0;//清计数器
					stop_cnt <= 2'b0;
				end
			end
			STATUS_DATA:
			begin
				if(clk_cnt == (PERIOD_CNT>>1))begin
					data_cnt <= data_cnt + 3'b1;
					uart_dout[data_cnt] <= uart_rxd;
				end
				if(next_status == STATUS_STOP)
					uart_done <= 1'b1;
				else
					uart_done <= 1'b0;
			end
			STATUS_VERIFY:
			begin
				if(next_status == STATUS_STOP)begin
					if((VERIFY_METHOD == VERIFY_EVEN && uart_rxd == ^uart_dout)||
					   (VERIFY_METHOD == VERIFY_ODD && uart_rxd == ~(^uart_dout)))
					   uart_done <= 1'b1;//校验成功
					else
					   uart_done <= 1'b0;
				end				
			end
			STATUS_STOP:
			begin
				if(clk_cnt == (PERIOD_CNT>>1))
					stop_cnt <= stop_cnt + 2'b1;		
			end
			default:
			begin
				data_cnt <= 3'b0;
				stop_cnt <= 2'b0;
				uart_done <= 1'b0;
			end
		endcase 
	end

end


endmodule

测试:实现数据回环

  • PC发送数据给FPGA串口,串口返回其发送的数据。
  • 实现代码
module uart_test(

	input  sys_clk,
	input  sys_rst_n,
	input  uart_rxd,
	
	output uart_txd
	
);
	

wire	    uart_tx_st;
wire	    uart_recv_done;	
wire [7:0]  uart_data_r;       

reg		    uart_recv_done_cur;
reg		    uart_recv_done_pre;

reg         uart_en_w;   
reg [7:0]   uart_data_w;
reg [7:0]   pulse_cnt;  //脉冲时间计数

uart_send uart_send_instance(

	.sys_clk	(sys_clk),
	.rst_n		(sys_rst_n),
	.uart_en	(uart_recv_done),   //发送使能
	.uart_din	(uart_data_r),     //待发送数据
	
	.uart_status(uart_tx_st),     //发送完成端口
    .uart_txd	(uart_txd)       //发送端口
	
);


uart_recv uart_recv_instance( 
	
	.sys_clk	(sys_clk),
	.rst_n      (sys_rst_n),
	.uart_rxd   (uart_rxd),         //接收端口
	
	.uart_done  (uart_recv_done),   //数据更新,输出一个单脉冲
	.uart_dout  (uart_data_r)       //数据输出
	
);

endmodule

你可能感兴趣的:(FPGA,FPGA,UART,状态机)