FPGA串口收发(二):串口接收 - 源代码与仿真测试
串行接收数据 1101_1000 ,转换为并行数据, 并显示 D8。
时钟40MHz,波特率115200
1、源文件
uart_rx.v
`timescale 1ns / 1ps
//
// Company: Myminieye
// Engineer: Nill
//
// Create Date: 2020-05-29
// Design Name:
// Module Name: uart_rx
// Project Name:
// Target Devices:
// Tool Versions:
// Description: 串口接收
// 三段式状态机,接收8位数据。波特率指定 115200
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
/
///
`define UD #1
module uart_rx #(
parameter BPS_NUM = 16'd347 // 时钟/波特率,用时钟周期构造波特率周期
// BPS_NUM = 40_000_000/115200 = 347.22 = 16'd347
// 1 bit位宽所需时钟周期的个数。最长的波特率计数,10417,二进制有14位,取16位
// parameter BPS_4800: 40MHz set 8333 ; 50MHz set 10417
// parameter BPS_9600: 40MHz set 4167 ; 50MHz set 5208
// parameter BPS_115200: 40MHz set 347; 50MHz set 434
)
(
//input ports
input clk,
//input rst_n, //没用上
input uart_rx, //接收模块串口接收信号线
//output ports
output reg [7:0] rx_data = 8'h00,
output reg rx_finish = 1'b0 //串口接收数据有效,接收完成拉高1个BPS
//output rx_end //接收到停止位,拉高1个clk,没啥用
);
//==========================================================================
//wire and reg 定义:信号与参数
//==========================================================================
localparam [15:0] BPS_NUM_MID = (BPS_NUM + 2'd1) / 2'd2 - 2'd1; //波特率周期的中间点
reg [3:0] rx_cur_st = 0; //current state of rx state machine.
reg [3:0] rx_nxt_st = 0; //next state of rx state machine.
reg uart_rx_1d; //保存uart_rx数据 1个时钟周期
reg uart_rx_2d; //保存uart_rx 前2个时钟周期
wire rx_start; //检测到start信号标志
reg [15:0] clk_div_cnt = 0; //波特周期计数,计到一个波特周期
reg [2:0] rx_bit_cnt = 0; //接收数据位bit计数
reg [7:0] rx_data_reg = 8'h00; //接收数据缓冲寄存器
//=================================================
// FSM Statement:状态声明
// 发送状态机4个状态:等待、发送起始位、发送数据、发送结束
//=================================================
//one hot with zero idle
localparam [3:0] IDLE = 4'b0000, //空闲状态,等待开始信号到来
RECEIVE_START = 4'b0001, //接收Uart开始信号,低电平一个波特周期.
RECEIVE_DATA = 4'b0010, //接收Uart传输数据信号,传输8bit,每个波特周期中间位置取值,8个周期后跳转到stop状态.
RECEIVE_STOP = 4'b0100, //停止状态数据线是高电平,与空闲状态是一致的,按照协议标准需要等待一个停止位周期再做状态跳转.
RECEIVE_END = 4'b1000; //结束中转状态.
//==========================================================================
//logic:逻辑信号初始化与判断
//==========================================================================
//双触发器(打两拍2个clk):全局时钟驱动2级触发器,将准稳态转为稳态
always @ (posedge clk)
begin
uart_rx_1d <= `UD uart_rx;
uart_rx_2d <= `UD uart_rx_1d;
end
//控制信号,接收模块的状态: 串口接收信号线从高电平转为低电平
assign rx_start = ((!uart_rx)&&(uart_rx_1d || uart_rx_2d));//等待两个clk后,再判断接收数据,保证数据稳定
//assign rx_end = (rx_cur_st == RECEIVE_END); //接收到停止位,拉高1个clk,没啥用
//时钟信号,波特周期计数器 division the clock to satisfy baud rate.
always @ (posedge clk)
begin
if( (clk_div_cnt == BPS_NUM) || (rx_cur_st == IDLE))
clk_div_cnt <= `UD 16'h0;
else
clk_div_cnt <= `UD clk_div_cnt + 16'h1;
end
//在接收数据状态中,接收的bit位计数,每一个波特周期计数加1
always @(posedge clk )
begin
if(rx_cur_st == IDLE)
rx_bit_cnt <= `UD 3'h0;
else if((rx_bit_cnt == 3'h7) && (clk_div_cnt == BPS_NUM))
rx_bit_cnt <= `UD 3'h0;
else if ((rx_cur_st == RECEIVE_DATA) && (clk_div_cnt == BPS_NUM))
rx_bit_cnt <= `UD rx_bit_cnt +3'h1;
else
rx_bit_cnt <= `UD rx_bit_cnt;
end
//=================================================
// FSM input:1st always block:sequential state transition
// 第一段:时序电路 - 现态与次态转换
//=================================================
//state change 状态跳转
always @ (posedge clk)
rx_cur_st <= rx_nxt_st; //下一状态赋给当前状态
//=================================================
// FSM Change: 2nd always block:combinational condition judgment
// 第二段:组合电路 tate change condition 状态跳转条件及规律
//=================================================
always @ (*)
begin
case(rx_cur_st)
IDLE :
begin
if(rx_start) //监测到start信号到来,下一状态跳转到start状态
rx_nxt_st = RECEIVE_START;
else
rx_nxt_st = rx_cur_st;//否则,状态保持不变
end
RECEIVE_START :
begin //已完成接收start标志信号
if(clk_div_cnt == BPS_NUM)
rx_nxt_st = RECEIVE_DATA;
else
rx_nxt_st = rx_cur_st;
end
RECEIVE_DATA :
begin //已完成8bit数据的传输
if((rx_bit_cnt == 3'h7) && (clk_div_cnt == BPS_NUM))
rx_nxt_st = RECEIVE_STOP;
else
rx_nxt_st = rx_cur_st;
end
RECEIVE_STOP :
begin //已完成接收stop标志信号
if(clk_div_cnt == BPS_NUM)
rx_nxt_st = RECEIVE_END;
else
rx_nxt_st = rx_cur_st;
end
RECEIVE_END :
begin
if(!uart_rx_1d) //数据线重新被拉低,表示新数据传输又发送start标志信号,需要跳转到start状态
rx_nxt_st = RECEIVE_START;
else //没有其他状况出现时,回到空闲状态,等待start信号的到来
rx_nxt_st = IDLE;
end
default : rx_nxt_st = IDLE;
endcase
end
//=================================================
// FSM Output:3rd always block:the sequential FSM output
// 第三段:状态输出,Moorer,判断当前状态Current State
//=================================================
//接收数据给输入:串行转并行
always @(posedge clk)
begin
case(rx_cur_st)
IDLE ,
RECEIVE_START :
begin
rx_finish <= `UD 1'b0;
rx_data_reg <= `UD 8'h0;
end
RECEIVE_DATA :
//-------------方法一、循环右移------------------------
begin //在一个波特周期的中间位置,取数据线上传输的数据,BPS_NUM=16'd347
//if( clk_div_cnt == BPS_NUM[15:1])
if( clk_div_cnt == BPS_NUM_MID ) //波特率周期的中间点
rx_data_reg <= `UD {uart_rx , rx_data_reg[7:1]};
end //uart_rx数据输入,每次传给缓冲寄存器的最高位,构建新的8位数据
//循环右移,Uart传输低位在前,最后一个bit刚好是最高位
//------------方法二、计数到相应位,直接赋值------------
/*
begin
case(rx_bit_cnt)
3'h0 : rx_data_reg [0] <= `UD uart_rx ;
3'h1 : rx_data_reg [1] <= `UD uart_rx ;
3'h2 : rx_data_reg [2] <= `UD uart_rx ;
3'h3 : rx_data_reg [3] <= `UD uart_rx ;
3'h4 : rx_data_reg [4] <= `UD uart_rx ;
3'h5 : rx_data_reg [5] <= `UD uart_rx ;
3'h6 : rx_data_reg [6] <= `UD uart_rx ;
3'h7 : rx_data_reg [7] <= `UD uart_rx ;
default: rx_data_reg <= `UD rx_data_reg ;//默认接收,保持不变
end
*/
RECEIVE_STOP :
begin
rx_finish <= `UD 1'b1; //串口接收数据有效,接收完成拉高1个BPS
rx_data <= `UD rx_data_reg;
//将缓冲寄存器的值赋值给输出寄存器,内部并行,可以一次性传8位
end
RECEIVE_END : rx_data_reg <= `UD 8'h0;
default : rx_finish <= `UD 1'b0;
endcase
end
endmodule
2、仿真文件testbench
uart_rx_tb.v
`timescale 1ns / 1ps
//
// Company: Myminieye
// Engineer: Nill
//
// Create Date:
// Design Name:
// Module Name:
// Project Name:
// Target Devices:
// Tool Versions:
// Description: 测试串口接收
// 模拟串口信号线,串行接收数据 1101_1000 ,转换为并行数据, 并显示 D8
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
`define UD #1
module tb_uart_rx();
//==========================================================================
//wire and reg 定义:信号与参数
//==========================================================================
// input to module
reg sim_clk; //模拟时钟信号
//reg tx_pulse; // active posedge
reg sim_rst_n;
reg uart_rx; //串口发送信号线
//output from module
wire [7:0] rx_data; //送入串口发送模块,准备发送的数据
wire rx_en; //串口接收数据有效,接收完成拉高1个BPS
wire rx_finish; //接收完成标志,拉高1个clk
//时钟参数
parameter SYS_CLK_FRE = 40_000_000; //系统频率40MHz 40_000_000
parameter SYS_CLK_PERIOD = 1_000_000_000/SYS_CLK_FRE; //周期25ns
parameter RST_CYCLE = 5; //复位持续时间,clk时钟周期数
parameter RST_TIME = RST_CYCLE * SYS_CLK_PERIOD; //复位时间:5个时钟周期
//波特率参数
parameter BAUD_RATE = 115200; //串口波特率
parameter BAUD_RATE_PERIOD = 1_000_000_000/BAUD_RATE;
//波特率周期,0.104ms = 104us,1/9600 s = 1^9 /9600 ns = 4167 sim_clk
//波特率周期, 1/115200 = 8680 ns = 8.7us = 347 sim_clk
//波特率+时钟参数
parameter [15:0] BPS_NUM = SYS_CLK_FRE / BAUD_RATE; //时钟/波特率,用时钟周期构造波特率周期
// BPS_NUM = 40_000_000/115200 = 347.22 = 16'd347
// 1 bit位宽所需时钟周期的个数。最长的波特率计数,10417,二进制有14位,取16位
// parameter BPS_4800: 40MHz set 8333 ; 50MHz set 10417
// parameter BPS_9600: 40MHz set 4167 ; 50MHz set 5208
// parameter BPS_115200: 40MHz set 347; 50MHz set 434
//==========================================================================
//模拟时钟信号
//==========================================================================
//模拟系统时钟:40MHz,25ns
always #((SYS_CLK_PERIOD+1)/2-1) sim_clk = ~sim_clk; //延时,电平翻转
initial begin
//模拟复位信号:一次,低电平5个clk
#0;
sim_clk = 1'b0;
sim_rst_n = 1'b0; //复位拉低,有效,
//#RST_TIME; //延时:保持足够长时间(至少5个clk)
#BAUD_RATE_PERIOD; //5个clk时间轴太短,仿真改为1个BPS,更明显
sim_rst_n = 1'b1; //解除复位
//==========================================================================
//模拟串口接收:串行信号输入,转化成并行数据,并显示
//==========================================================================
uart_rx = 1'b1; //串口发送线,默认拉高
repeat( BPS_NUM*1 ) @(posedge sim_clk); //循环347个时钟周期,即一个波特率周期
//#BAUD_RATE_PERIOD; //直接延时,一个波特率周期
$display("Initialization complete. BAUD_RATE is %d",BAUD_RATE); //命令行显示初始化完成,输出BPS_NUM
//发送起始位
uart_rx = 1'b0;
#BAUD_RATE_PERIOD;
//串行数据,一位一位送入接收信号线:***从位0到位7***,依次发送
//测试数据为8'hD8=8'b1101_1000
uart_rx = 1'b0;
#BAUD_RATE_PERIOD;
uart_rx = 1'b0;
#BAUD_RATE_PERIOD;
uart_rx = 1'b0;
#BAUD_RATE_PERIOD;
uart_rx = 1'b1;
#BAUD_RATE_PERIOD;
uart_rx = 1'b1;
#BAUD_RATE_PERIOD;
uart_rx = 1'b0;
#BAUD_RATE_PERIOD;
uart_rx = 1'b1;
#BAUD_RATE_PERIOD;
uart_rx = 1'b1;
#BAUD_RATE_PERIOD;
$display("The uart_rx 8'hD8 = 8'b1101_1000 has been sent."); //命令行显示:串口信号线数据已发送
//串口:结束位
uart_rx = 1'b1;
#BAUD_RATE_PERIOD;
$display("The uart_rx has received. rx_data = 8'h%h",rx_data);
//命令行显示:串口信号线接收已结束,显示接收到的数据
repeat( BPS_NUM*5 ) @(posedge sim_clk);
$stop; //结束仿真
end
//==========================================================================
//调用top模块
//==========================================================================
//串口发送
uart_rx #(
//.CLK_SYS ( SYS_CLK_FRE), //系统时钟
.BPS_NUM ( BPS_NUM ) // 时钟/波特率,1 bit位宽所需时钟周期的个数
)
u_uart_rx(
.clk ( sim_clk ),// input clk,
.rstn ( sim_rstn ),// input
.uart_rx ( uart_rx ),// input reg 串口接收信号线
.rx_data ( rx_data ),// output 接收到的数据
.rx_en ( rx_en ),// output 串口接收数据有效,接收完成拉高1个BPS
.rx_finish( rx_finish) // output 接收完成标志,拉高1个clk
);
endmodule
3、仿真结果
Modelsim波形
移位结果
rx_data_reg <= `UD {uart_rx , rx_data_reg[7:1]};
命令行显示