Verilog实现UART接收机

放一个很详细的rs232串口解析:5. 串口rs232 — [野火]FPGA Verilog开发实战指南——基于Altera EP4CE10 征途Pro开发板 文档 (embedfire.com)

代码是参照了野火fpga例程写的,不过接收数据开始标志(start_neg)我改成了只在下降沿到来且uart没有开始接收数据时有效。

解决了野火例程中提到的这个问题:

我们检测到了第一个下降沿,后面的信号将以下降沿标志信号start_nedge为条件开始接收一帧10bit的数据。但新的问题又出现了,我们的rx信号本身就是1bit的,如果在判断第一个下降沿后,后面帧中的数据还可能会有下降沿出现,那我们会又产生一个start_nedge标志信号, 这样就出现了误判断,那我们该如何避免这种情况呢?

系统时钟为50MHz,采用的波特率为9600,有效数据为8位,在每个数据的中间值进行采样。以上几个数据我都改成参数化设置,方便后续调整。 

代码如下:

module uart_rx(
	input wire			sys_clk		,  //时钟
	input wire 			sys_rst_n	,  //复位,低电平有效

	input wire			uart_rx_i	,  //uart接收机串行输入
	output reg [7:0]	uart_rx_o	,  //uart接收机并行输出
	output reg			uart_rx_o_vld  //uart接收机输出数据有效标志
);

parameter 	BAUD_CNT_MAX = 13'd5208;
parameter	BIT_CNT = 4'd8;
parameter	BIT_FALG = BAUD_CNT_MAX >> 1;

reg 		uart_rx_i_reg1;
reg 		uart_rx_i_reg2;
reg 		uart_rx_i_reg3;
reg 		work_en;
reg [12:0]	baud_cnt;
reg 		bit_flag;
reg [3:0]	bit_cnt;
reg 		rx_flag;
reg [7:0]	rx_data;

wire 		start_neg;
wire 		end_work;

//打两拍进行跨时钟域处理
//两拍后时钟稳定,再与第三拍组合实现下降沿检测
always @(posedge sys_clk or negedge sys_rst_n) begin
	if(!sys_rst_n) begin
		uart_rx_i_reg1 <= 1'b1;
		uart_rx_i_reg2 <= 1'b1;
		uart_rx_i_reg3 <= 1'b1;
	end
	else begin
		uart_rx_i_reg1 <= uart_rx_i;
		uart_rx_i_reg2 <= uart_rx_i_reg1;
		uart_rx_i_reg3 <= uart_rx_i_reg2;
	end
end

//根据work_en状态来判断下降沿是否有效
//只有第一个下降沿能作为开始信号标志
assign start_neg = (work_en == 1'b0 && (uart_rx_i_reg3 & !uart_rx_i_reg2));

//指示uart_rx状态,1表示正在接收,0表示接收结束
always @(posedge sys_clk or negedge sys_rst_n) begin
	if(!sys_rst_n) begin
		work_en <= 1'b0;
	end
	else if(start_neg) begin
		work_en <= 1'b1;
	end
	else if(end_work) begin
		work_en <= 1'b0;
	end
end

//当baud_cnt从0记到最大值时,表明已经接收到1bit数据
//计数范围0~BAUD_CNT_MAX-1
always @(posedge sys_clk or negedge sys_rst_n) begin
	if(!sys_rst_n) begin
		baud_cnt <= 13'b0;
	end
	else if(work_en) begin
		if(baud_cnt == BAUD_CNT_MAX-1) begin
			baud_cnt <= 13'd0;
		end
		else begin
			baud_cnt <= baud_cnt + 1'b1;
		end
	end
	else begin
		baud_cnt <= 13'd0;
	end
end

//中间位置的数据最稳定,bit_flag表明此时可以寄存数据
always @(posedge sys_clk or negedge sys_rst_n) begin
	if(!sys_rst_n) begin
		bit_flag <= 1'b0;
	end
	else if(baud_cnt == BIT_FALG - 1'b1) begin
		bit_flag <= 1'b1;
	end
	else begin
		bit_flag <= 1'b0;
	end
end

//记录bit数,只有表示数据的8bit才是有用的
//计数范围0~8,0是开始位,1~8是数据位
always @(posedge sys_clk or negedge sys_rst_n) begin
	if(!sys_rst_n) begin
		bit_cnt <= 4'd0;
	end
	else if(bit_flag) begin
		if(bit_cnt == BIT_CNT) begin
			bit_cnt <= 4'd0;
		end
		else begin
			bit_cnt <= bit_cnt + 1'b1;
		end
	end
end

//串行输入数据转成并行输出数据,因为上位机先发低位再发高位,所以数据右移
always @(posedge sys_clk or negedge sys_rst_n) begin
	if(!sys_rst_n) begin
		rx_data <= 8'd0;
	end
	else if((bit_cnt >= 4'd1) && (bit_cnt <= 4'd8) && (bit_flag)) begin
		rx_data <= {uart_rx_i_reg3,rx_data[7:1]};
	end
end

//移位完成信号
always @(posedge sys_clk or negedge sys_rst_n) begin
	if(!sys_rst_n) begin
		rx_flag <= 1'b0;
	end
	else if(bit_cnt == BIT_CNT && bit_flag) begin
		rx_flag <= 1'b1;
	end
	else begin
		rx_flag <= 1'b0;
	end
end

//接收机接收结束标志
assign end_work = (bit_cnt == BIT_CNT && bit_flag) ? 1'b1 : 1'b0;

//寄存稳定的8bit数据
always @(posedge sys_clk or negedge sys_rst_n) begin
	if(!sys_rst_n) begin
		uart_rx_o <= 8'd0;
	end
	else if(rx_flag) begin
		uart_rx_o <= rx_data;
	end
end

//输出数据有效标志
always @(posedge sys_clk or negedge sys_rst_n) begin
	if(!sys_rst_n) begin
		uart_rx_o_vld <= 1'b0;
	end
	else begin
		uart_rx_o_vld <= rx_flag;
	end
end

endmodule

testbench如下:

`timescale 1ns/1ns

module tb_uart_rx();


//\* Parameter and Internal Signal \//


//reg define
reg sys_clk;
reg sys_rst_n;
reg uart_rx_i;

//wire define
wire [7:0] uart_rx_o;
wire uart_rx_o_vld;


//\* Main Code \//


//初始化系统时钟、全局复位和输入信号
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
uart_rx_i <= 1'b1;
#20;
sys_rst_n <= 1'b1;
end

//模拟发送8次数据,分别为0~7
initial begin
#200
uart_rx_i_bit(8'd0); //任务的调用,任务名+括号中要传递进任务的参数
uart_rx_i_bit(8'd1);
uart_rx_i_bit(8'd2);
uart_rx_i_bit(8'd3);
uart_rx_i_bit(8'd4);
uart_rx_i_bit(8'd5);
uart_rx_i_bit(8'd6);
uart_rx_i_bit(8'd7);
end

//sys_clk:每10ns电平翻转一次,产生一个50MHz的时钟信号
always #10 sys_clk = ~sys_clk;

//定义一个名为uart_rx_i_bit的任务,每次发送的数据有10位
//data的值分别为0~7由j的值传递进来
//任务以task开头,后面紧跟着的是任务名,调用时使用
task uart_rx_i_bit(
//传递到任务中的参数,调用任务的时候从外部传进来一个8位的值
input [7:0] data
);
integer i; //定义一个常量
//用for循环产生一帧数据,for括号中最后执行的内容只能写i=i+1
//不可以写成C语言i=i++的形式
for(i=0; i<10; i=i+1) begin
case(i)
0: uart_rx_i <= 1'b0;
1: uart_rx_i <= data[0];
2: uart_rx_i <= data[1];
3: uart_rx_i <= data[2];
4: uart_rx_i <= data[3];
5: uart_rx_i <= data[4];
6: uart_rx_i <= data[5];
7: uart_rx_i <= data[6];
8: uart_rx_i <= data[7];
9: uart_rx_i <= 1'b1;
endcase
#(5208*20); //每发送1位数据延时5208个时钟周期
end
endtask //任务以endtask结束


//\* Instantiation \//

//------------------------uart_uart_rx_i_inst------------------------
uart_rx uart_rx_inst(
 .sys_clk (sys_clk ), //input sys_clk
 .sys_rst_n (sys_rst_n ), //input sys_rst_n
 .uart_rx_i (uart_rx_i ), //input uart_rx_i

 .uart_rx_o (uart_rx_o ), //output [7:0] uart_rx_o
 .uart_rx_o_vld (uart_rx_o_vld ) //output uart_rx_o_vld
 );

 endmodule

本人博客写得潦草至极......如果有错的话请及时指出,后续我也会慢慢把文章补充得更加完整(应该),同时我也希望对自己的代码进行优化修改,如果有路过的大佬愿意指点一二的话就再好不过了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值