根据代码综合出来的RTL电路图可以当成原理框图来看:
程序模块分为顶层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<