一、要点
参考上一篇发送模块https://blog.csdn.net/a1254484594/article/details/100190162
二、实现
1.架构:使用有限状态机实现,主要包含两部分:数据采样、状态转移。
数据采样一般在每个数据位的中间采样,因为此时离上升沿或下降沿最远。
状态机可定义11个状态,对应到一帧数据中,依照时间顺序为:WAIT等待起始位状态,STARD起始位,D0、D1、D2、D3、 D4、D5、D6、D7 8bit数据,END停止位。使用一个计数器,定时一定波特率下每bit所需要的时间,在各状态间进行转移。
2.新建一个uart_rx.v文件,使用Verilog语言进行描述
`timescale 1ns/1ps
module uart_rx(
input clk,
input nrst,
input rx_en,
input rx_pin,
output reg [7:0]rx_data,
output reg rx_done
);
parameter INPUT_CLK = 125000000 , BUDO = 115200;
localparam B_CNT_MAX = (INPUT_CLK/BUDO)-1;//波特率计数器最大值
parameter [3:0]
WAIT = 4'd12, //等待起始位状态
STARD= 4'd13, //起始位
END = 4'd8, //停止位
D0 = 0, D1 = 1, D2 = 2, D3 = 3, D4 = 4, D5 = 5, D6 = 6, D7 = 7;//8bit数据
reg [3:0]CS; //状态
reg [10:0]b_cnt; //用来产生波特率时钟
以上定义一个uart_tx模块,定义端口信号,定义所需参数、信号
//状态转移逻辑
always @(posedge clk) begin
if (!nrst) begin//复位
CS <= WAIT;
b_cnt <= 'd0;
end
else if(rx_en) begin
case(CS)
WAIT:/*等待起始位*/
begin
if(!rx_pin) CS <= STARD;//rx被拉低后进入起始位
else CS <= CS;
end
STARD:/*起始位*/
begin
if (b_cnt == B_CNT_MAX) begin//计数满1bti时间,计数器清零,进入第一bit的接收
CS <= 4'd0; //转移到接收第一bit的状态
b_cnt <= 'd0;
end
else begin //计数中
b_cnt <= b_cnt + 1'b1;
CS <= CS;
end
end
END:/*停止位*/
begin
if (b_cnt == B_CNT_MAX) begin//计数满1bti时间,计数器清零,进入等待状态
CS <= WAIT; //转移到等待状态
b_cnt <= 'd0;
end
else begin //计数中
b_cnt <= b_cnt + 1'b1;
CS <= CS;
end
end
D0,D1,D2,D3,D4,D5,D6,D7:/*采样数据*/
begin
if (b_cnt == B_CNT_MAX) begin//计数满1bti时间,计数器清零,进入下一bit
CS <= CS + 1'b1; //转移到下一bit
b_cnt <= 'd0;
end
else begin //计数中
b_cnt <= b_cnt + 1'b1;
CS <= CS;
end
end
default:
begin
CS <= CS;
b_cnt <= 'd0;
end
endcase
end //end else if (rx_en)
end
状态转移部分,在rx_en有效时才进行转移操作,配合使用寄存器b_cnt计数,实现等待起始位、起始位、8各数据位、停止位的依次转移。
//根据CS状态和b_cnt计数器值采样rx_pin
always @(posedge clk) begin
if (!nrst) begin//复位
rx_data <= 8'd0;
rx_done <= 1'b0;
end
else if (CS < 'd8) begin
rx_done <= 1'b0;
if (b_cnt == B_CNT_MAX/2)//在中点采样
rx_data[CS] <= rx_pin;
else
rx_data <= rx_data;
end
else if (CS == 'd8) begin
if (!rx_pin) //停止位错误
rx_done <= 1'b0;
else
rx_done <= 1'b1;
end
else begin
rx_data <= rx_data;
rx_done <= rx_done;
end
end
endmodule // uart_rx
根据状态寄存器CS,再判断计数寄存器b_cnt的值,在b_cnt等于最大值的一半,即中间时刻进行采样。
至此接收模块完成。
三、测试文件编写,VCS仿真
1.测试使用上篇文章的发送代码,在此基础上再实例化一个接收模块,代码如下
`timescale 1ns/1ps
module usart_tb();
reg nrst;
reg clk;
reg en_tx;
reg [7:0] tx_data;
wire tx_pin,tx_done;
reg [7:0]tx_cnt;
reg en_rx;
wire [7:0] rx_data;
wire rx_done;
//实例化
uart_tx uart_tx(
.clk(clk),
.nrst(nrst),
.tx_en(en_tx),
.tx_data(tx_data),
.tx_pin(tx_pin),
.txdone(tx_done)
);
uart_rx uart_rx(
.clk(clk),
.nrst(nrst),
.rx_en(en_rx),
.rx_pin(tx_pin),//txpin连接到rxpin
.rx_data(rx_data),
.rx_done(rx_done)
);
//产生125Mhz的时钟
initial begin
clk = 0;
forever begin
#4;
clk = ~clk;
end
end
initial begin
en_tx = 0;
tx_data = 8'b0;
tx_cnt = 'd0;
nrst = 1;
#100;
nrst = 0;//复位信号
#103;
nrst = 1;
tx_data = 'd64;
en_rx = 1;
end
//产生循环发送的信号
always @(negedge clk) begin
if (nrst) begin
if (tx_done) begin
if (en_tx)//刚刚发送完成
en_tx <= 1'b0;
else begin//发送完成一段时间
if (tx_cnt == 'd250) begin
tx_cnt <= 'd0;
en_tx <= 1'b1;//重启发送
end
else
tx_cnt <= tx_cnt + 1'b1;
end
end
else begin
en_tx <= en_tx;
tx_cnt <= tx_cnt;
end
end
end
endmodule // usart_tb