最近报了个小项目,跟FPGA相关的,想着也很久没写verilog了,写个串口通信状态机练手。
串口通信,玩过单片机的基本上都知道,但是玩单片机对原理上没有太多要求,所以我在写verilog实现的时候我才知道我对Uart一无所知,真是惭愧。
我这里写的是异步通信的实现,如有不当,多多指教。话不多说,先上代码。
/2024-3-12 千sir
//串口通信接收的verilog实现
//首先定义总模块
module uart_rx(
input clk,//时钟
input rst,//使能端口
input data_rx,//数据接收端
output reg data_tx//数据发送端
);
reg [7:0]serial_data;//设置数据寄存器
reg [15:0] clk_cnt;//设置计数器
reg [2:0] state;//设置状态寄存器
reg rx_delay;//rx的延时信号
reg [3:0] bit_cnt;//比特计数
parameter clock_fre=50000000;//设置系统时钟为50M
parameter biterate =115200;//设置波特率为115200
parameter cnt_period=clock_fre/biterate;//计数周期
//以下是接收过程
always@(posedge clk or negedge rst)begin
if(!rst)begin
state<=0;clk_cnt<=0;serial_data<=0;rx_delay<=0;data_tx<=1;//复位端低电平使能后,除串口发送端之外全部清零,串口发送端置高电平
end
else begin
rx_delay<=data_rx;
case(state)
0://空闲状态
if(rx_delay&(~data_rx))begin
data_tx<=0;
state<=1;//转换到起始状态
end
else begin
data_tx<=1;
end
1://起始位
if(clk_cnt==cnt_period)begin//如果经过了一个接收周期
state<=2;//进入接收状态
end
else begin
clk_cnt<=clk_cnt+1;
end
2://接收位
if(clk_cnt==cnt_period)begin
if(bit_cnt==7)begin//接收八个比特数据后转换到结束位
state<=3;
end
else begin
serial_data[bit_cnt]=data_rx;//给数据寄存器赋值
clk_cnt<=0;//时间计数归零
bit_cnt=bit_cnt+1;//比特数加一
end
end
else begin
clk_cnt=clk_cnt+1;
end
3://结束位
if(~rx_delay&data_rx==1)begin
state<=0;//检测到高电平就回到空闲状态
end
else begin
clk_cnt<=0;
end
default:begin//case配套,避免产生锁存器
state<=0;
end
endcase
end
end
endmodule
刚开始写的时候连波特率都需要去百度了,一年单片机裸机跟没做过一样,下面简要介绍一下两个专业名词:
波特率:每秒钟发送的比特数,一般是9600或者115200
系统时钟:系统每秒钟响应的周期数。它与波特率的商就是发送一个比特需要的时钟周期数。
其实异步通信是有起始位、数据位、校验位和终止位的,但是我懒得写校验位了(doge),一般都默认无校验的。
以上代码是接收模块。至于发送模块,写来也差不多,不加赘述。
下面写个简单的testbench
//2024-3-14 千sir
//串口通信状态机的testbench
`timescale 1ns/1ps
//定义整体模块
module uart_rx_tb;
reg [7:0] serial;
reg clk,rst,data_rx;
reg [15:0] clk_cnt;//设置计数器
parameter clock_fre=50000000;//设置系统时钟为50M
parameter biterate =115200;//设置波特率为115200
parameter cnt_period=clock_fre/biterate;//计数周期
reg [3:0] bite_cnt;//比特计数
uart_rx u_uart_rx(.clk(clk),.rst(rst),.data_rx(data_rx),.data_tx(tx));
initial begin//初始化
rst<=0;
clk<=0;
serial<="A";
#5000 $stop;
end
begin
always #2.5 clk=~clk;//时钟每2.5纳秒翻转一次,即频率为50M
always@(posedge clk)begin
data_rx=0;
if(clk_cnt==cnt_period)begin
data_rx<=serial[bite_cnt];
bite_cnt<=bite_cnt+1;
if(bite_cnt==7)begin
data_rx=1;
end
end
else begin
clk_cnt<=clk_cnt+1;
end
end
end
endmodule
发送一个简单的“A”给系统,仿真结果正好是01000001,即65,在ASCII码表中代表”A”.
其实我觉得我的testbench写的挺奇怪的,不过先记录下来,等学精了再来改。(坐等打脸)