根据代码综合出来的RTL电路图可以当成原理框图来看:

UART的回环实例_数据

程序模块分为顶层Uart_top、发送模块uart_tx、接收模块uart_rx以及时钟产生模块clk_div。uart_rx将收到的包解析出8位数据,再传送给uart_tx发出,形成回环。参考时钟为100Mhz,波特率为9600bps。例子使用最简单的串口设置,没有校验位。

各模块程序如下:

顶层:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date:    20:44:47 03/04/2019 
// Design Name: 
// Module Name:    Uart 
//
//
module Uart_top(
	output txd,
	input rxd,
	input clk
    );
	wire clk_9600;
	wire receive_ack;
	wire [7:0] data;
	
	//串口发送模块
	uart_tx uart_tx_u1(
		.clk(clk_9600),
		.txd(txd),
		.rst(1),
		.data_o(data),
		.receive_ack(receive_ack)
	);
	
	
	//串口接收模块
	uart_rx uart_rx_u1(
	.rxd(rxd),
	.clk(clk_9600),
	.receive_ack(receive_ack),
	.data_i(data)
    );
	
	//时钟模块
	clk_div clk_div_u1(
	.clk(clk),
	.clk_out(clk_9600)
	);

	
endmodule

接收模块:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date:    20:52:35 03/04/2019 
// Design Name: 
// Module Name:    uart_rx 
// Project Name: 

//
module uart_rx(
	input rxd,
	input clk,
	output receive_ack,
	output reg [7:0] data_i
    );
	//串口接收状态机分为三个状态:等待,接收,接收完成
	localparam IDLE = 0,
				RECEIVE = 1,
				RECEIVE_END = 2;
	
	reg [3:0] cur_st,nxt_st; //状态机变量
	reg [4:0] count;
	
	always@(posedge clk) begin
		cur_st <= nxt_st;
	end
	
	always@( * ) begin
		nxt_st = cur_st;
		case(cur_st)
			IDLE: if(!rxd) nxt_st = RECEIVE;  //接收到开始信号,开始接收数据
			RECEIVE: if(count == 7) nxt_st = RECEIVE_END; //八位数据接收计数
			RECEIVE_END: nxt_st = IDLE; //接收完成
			default: nxt_st = IDLE;
		endcase	
	end
	
	always@(posedge clk) begin //接收数据计数
		if(cur_st == RECEIVE) count <= count + 1;
		else if(cur_st == IDLE|cur_st == RECEIVE_END) 
			count <= 0;
	end
	
	always@(posedge clk) begin //从高到底发送数据
	if(cur_st == RECEIVE) begin
		data_i[6:0] <= data_i[7:1];
		data_i[7] <= rxd;
	end
	
	end
	
	assign receive_ack = (cur_st == RECEIVE_END)?1:0; //接收完成时回复信号 1,否则 0
	
	
endmodule

发送模块:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date:    20:52:52 03/04/2019 
// Design Name: 
// Module Name:    uart_tx 
// Project Name: 
//
module uart_tx(
	input [7:0] data_o,
	output reg txd,
	input clk,
	input rst,
	input receive_ack
    );
	//发送状态机分为四个状态:等待、发送起始位、发送数据、发送结束
	localparam IDLE = 0,
				SEND_START = 1,
				SEND_DATA = 2,
				SEND_END = 3;
	
	reg [3:0] cur_st,nxt_st;
	reg [4:0] count;
	reg [7:0] data_o_tmp;
	
	always@(posedge clk)
		cur_st <= nxt_st;
		
	always@(*) begin
		nxt_st = cur_st;
		case(cur_st)
			IDLE: if(receive_ack == 1) nxt_st = SEND_START; //接收完成时开始发送数据
			SEND_START: nxt_st = SEND_DATA; //发送起始位
			SEND_DATA: if(count == 7) nxt_st = SEND_END; //发送八位数据
			SEND_END: if(receive_ack == 1) nxt_st = SEND_START; //发送结束
			default: nxt_st = IDLE;
		endcase
	end
	
	always@(posedge clk)
		if(cur_st == SEND_DATA)
			count <= count + 1;
		else if(cur_st == IDLE|cur_st == SEND_END)
			count <= 0;
	
	always@(posedge clk) //发送低位到高位
		if(cur_st == SEND_START)
			data_o_tmp <= data_o; //将发送数据导入变量
		else if(cur_st == SEND_DATA)
			data_o_tmp[6:0] <= data_o_tmp[7:1]; //每发送一位数据后将data_o_tmp右移一位,便于下一个数据的发送
			
			
	always@(posedge clk)
	if(cur_st == SEND_START)
		txd <= 0;
	else if(cur_st == SEND_DATA)
		txd <= data_o_tmp[0]; //由于每次发送后右移,所以每次发送最低位
	else if(cur_st == SEND_END)
		txd <= 1;
			
	

endmodule

时钟分频模块:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date:    20:53:08 03/04/2019 
// Design Name: 
// Module Name:    clk_div 
// Project Name: 
//
module clk_div(
	input clk,
	output reg clk_out
    );
	localparam Baud_Rate = 9600;
	localparam Div_Num = 'd100_000_000/Baud_Rate; //分频数为时钟速率除以波特率
	
	reg [15:0] num;
	
	always@(posedge clk)
	if(num == Div_Num) begin
		num <= 0;
		clk_out <= 1;
	end
	else begin
		num <= num + 1;
		clk_out <= 0;
	end


endmodule

有关串口协议的东西还是第一次涉猎,还有些没有弄懂,例如系统时钟和波特率时钟之间的关系?这里记录再此,回头再看吧。

这种忙成狗子的日子。


更新:2019/05/27

系统时钟和波特率之间的关系是一种分频关系,具体的实现方法见博文:

RS232 波特率时钟产生方法?

将我最新产生的波特率时钟的参数化方法记录于此:

module BaudGen #(
	parameter Clkfrequency = 25000_000,
	parameter Baud = 115200,
	parameter Oversampling = 8	

)
(
	input clk,
	input enable,
	output BaudTick

    );
	
	parameter Ratio = Clkfrequency/(Baud*Oversampling);
	parameter AddWidth = 16;
	parameter Inc = (1<<AddWidth)/Ratio;
	
	reg [AddWidth:0] acc = 0;
	
	always@(posedge clk) begin
		if(enable) begin
			acc <= acc[AddWidth - 1 : 0] + Inc;
		end
		else begin
			acc <= Inc;
		end
	
	end
	assign BaudTick = acc[AddWidth];
	
	
endmodule