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]};
命令行顯示