串口發送的數據:0 -> A (1010) -> C (1100) ->0
時鐘40MHz,波特率115200
1、源文件
uart_tx.v
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Myminieye
// Engineer: Nill
//
// Create Date: 2020-05-29
// Design Name:
// Module Name: uart_tx
// Project Name:
// Target Devices:
// Tool Versions:
// Description: 串口發送
// 三段式狀態機,發送8位數據。波特率指定 115200
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
/////////////////////////////////////////////////////////////////////////////////
`define UD #1
module uart_tx #(
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 clk, //clock
input [7:0] tx_data, //等待發送的字節數據 uart tx data signal byte;
input tx_pulse, //外部傳入,觸發串口發送
//input rx_finish, //接收應答,表示接收完成(自發自收)
output reg uart_tx, // 發送模塊串口發送信號線 uart tx transmit data line
output tx_busy, // 發送模塊忙狀態指示 uart tx module work states,high is busy;
//仿真信號
output reg tx_finish = 1'b0 //串口發送數據結束標誌,8位數據發完,拉高一個BPS
);
//==========================================================================
//wire and reg 定義:信號與參數
//==========================================================================
//parameter BPS_NUM_MID = (BPS_NUM + 2'd1) / 2'd2 - 2'd1; //波特率週期的中間點
reg [3:0] tx_cur_st = 0; //發送 當前狀態
reg [3:0] tx_nxt_st = 0; //發送 下一狀態
reg tx_pulse_reg = 0; //發送觸發信號緩存
reg [3:0] pulse_delay_cnt = 0; //觸發延時
reg tx_en = 0; //開啓串口發送,整個發送週期都拉高
//data_gen模塊 ,產生數據後,就開啓串口發送 (write_en)->tx_en
reg [2:0] tx_bit_cnt = 0; //發送位數,計數
reg [15:0]clk_div_cnt = 0; //波特週期計數,計到一個波特週期
reg [7:0] tx_data_reg = 0; //發送數據緩衝寄存器
//=================================================
// FSM Statement:狀態聲明
// 發送狀態機4個狀態:等待、發送起始位、發送數據、發送結束
//=================================================
//one hot with zero idle
localparam [3:0] IDLE = 4'b0000, //tx state machine's state.空閒狀態
SEND_START = 4'b0001, //tx state machine's state.發送start狀態
SEND_DATA = 4'b0010, //tx state machine's state.發送數據狀態
SEND_STOP = 4'b0100, //tx state machine's state.發送stop狀態
SEND_END = 4'b1000; //tx state machine's state.發送結束狀態
//==========================================================================
//logic:邏輯信號初始化與判斷
//==========================================================================
//發送模塊:忙碌狀態
assign tx_busy = (tx_cur_st != IDLE);//非空閒,發送態忙
//tx_cur_st = SEND_XXX,正在發送,忙狀態
//tx_cur_st = IDLE, 發送結束了/空閒狀態,不忙
//發送模塊這8位發完了,傳出tx_busy不忙,進入data_gen,又返回發送模塊,tx_pulse觸發下一組8位發送
//觸發串口發送 tx_pulse 0->1
//打一拍,D觸發器,構造上升沿觸發
always @ (posedge clk)
begin
tx_pulse_reg <= `UD tx_pulse;
end
//開啓串口發送狀態 tx_en:整個發送週期都拉高
always @(posedge clk )
begin
// rstn復位 tx_en=0,在 data_gen中實現
if(~tx_pulse_reg & tx_pulse) //tx_pulse 上升沿觸發
tx_en <= 1'b1;
else if(tx_cur_st == SEND_END) //串口發送結束,關閉串口發送
tx_en <= 1'b0;
end
//時鐘信號,波特週期計數器(時鐘分頻)
always @ (posedge clk)
begin //發送觸發信號,且打了一拍,時間也計到了一個波特週期
if( (clk_div_cnt == BPS_NUM) || (~tx_pulse_reg & tx_pulse))
clk_div_cnt <= `UD 16'h0;
//clk_out <= 16'h1;
else
clk_div_cnt <= `UD clk_div_cnt + 16'h1;
//clk_out <= 16'h0;
end
//發送數據狀態中,發送bit位計數(8),以波特週期累加 count the number has transmited
always @(posedge clk )
begin
if(!tx_en)
tx_bit_cnt <= `UD 3'h0;
else if((tx_bit_cnt == 3'h7) && (clk_div_cnt == BPS_NUM))
tx_bit_cnt <= `UD 3'h0;
else if ((tx_cur_st == SEND_DATA) && (clk_div_cnt == BPS_NUM))
tx_bit_cnt <= `UD tx_bit_cnt +3'h1;
else
tx_bit_cnt <= `UD tx_bit_cnt;
end
//觸發信號延時:觸發條件進來,延時15個clk (4'hf)
always @(posedge clk)
begin
if (tx_cur_st != IDLE)
pulse_delay_cnt <= `UD 4'hf;
else
pulse_delay_cnt <= pulse_delay_cnt + 4'h1;
end
//=================================================
// FSM input:1st always block:sequential state transition
// 第一段:時序電路 - 現態與次態轉換
//=================================================
//state change 狀態跳轉
always @ (posedge clk)
tx_cur_st <= tx_nxt_st; //下一狀態賦給當前狀態
//=================================================
// FSM Change: 2nd always block:combinational condition judgment
// 第二段:組合電路 tate change condition 狀態跳轉條件及規律
//=================================================
always @ (*)
begin
case(tx_cur_st)
IDLE: //4'b0000
begin //觸發發送,15個時鐘週期延時,再轉到發送start狀態
if( (tx_en) & (pulse_delay_cnt == 4'hf) )
tx_nxt_st = SEND_START;//接收完成,可以開始發送數據
else
tx_nxt_st = tx_cur_st;//否則,狀態保持不變
end
SEND_START: //4'b0001
begin //發送一個波特週期的低電平後進入,發送數據狀態
if(clk_div_cnt == BPS_NUM)
tx_nxt_st = SEND_DATA;
else
tx_nxt_st = tx_cur_st;
end
SEND_DATA: //4'b0010
begin
if((tx_bit_cnt == 3'h7) && (clk_div_cnt == BPS_NUM))
tx_nxt_st = SEND_STOP;
else
tx_nxt_st = tx_cur_st;
end
SEND_STOP: //4'b0100
begin //計時8個波特週期後(發送了8bit數據),跳轉到發送stop狀態
if(clk_div_cnt == BPS_NUM)
tx_nxt_st = SEND_END;
else
tx_nxt_st = tx_cur_st;
end
SEND_END: //4'b1000
tx_nxt_st = IDLE; //變化太快,1個clk,而不是1個BPS就跳轉了
default: tx_nxt_st = IDLE;
endcase
end
//=================================================
// FSM Output:3rd always block:the sequential FSM output
// 第三段:狀態輸出,Moorer,判斷當前狀態Current State
//=================================================
//發送數據給輸出:並行轉串行
always @(posedge clk) begin
if(tx_en) begin
case(tx_cur_st)
IDLE : uart_tx <= `UD 1'h1;
SEND_START : begin
uart_tx <= `UD 1'h0; //發送數據,先把串口線拉低
tx_finish <= `UD 1'b0; //串口發送數據結束標誌,先拉低
end
SEND_DATA : begin
/* //-------------方法一、循環右移------------------------
tx_data_reg[6:0] <= `UD tx_data_reg[7:1];
//已經有了完整8位數據,循環右移,高位依次移到低位/最低位0
uart_tx <= `UD tx_data_reg[0];
//uart_tx數據輸出, 每次把緩衝寄存器的最低位0傳出去
//同一個clk觸發 數據發送與移位,每次發送的是上一次的移位結果
*/
//------------方法二、計數到相應位,直接賦值------------
case(tx_bit_cnt)
3'h0 : uart_tx <= `UD tx_data[0];
3'h1 : uart_tx <= `UD tx_data[1];
3'h2 : uart_tx <= `UD tx_data[2];
3'h3 : uart_tx <= `UD tx_data[3];
3'h4 : uart_tx <= `UD tx_data[4];
3'h5 : uart_tx <= `UD tx_data[5];
3'h6 : uart_tx <= `UD tx_data[6];
3'h7 : uart_tx <= `UD tx_data[7];
default : uart_tx <= `UD 1'h1;
endcase
end
SEND_STOP : begin
uart_tx <= `UD 1'h1; //發送停止狀態 輸出1個波特週期高電平
//每次輸出,uart_tx串行只傳一位,不能直接把tx_data_reg 並行輸出
tx_finish <= `UD 1'b1; //串口發送數據結束標誌,拉高
end
default : begin
uart_tx <= `UD 1'h1; // 其他狀態默認與空閒狀態一致,保持高電平輸出
tx_finish <= `UD 1'b0;
end
endcase
//case(tx_cur_st)
end
else
uart_tx <= `UD 1'h1;
end
endmodule
//module uart_tx
2、仿真文件testbench
uart_tx_tb.v
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Myminieye
// Engineer: Nill
//
// Create Date:
// Design Name:
// Module Name:
// Project Name:
// Target Devices:
// Tool Versions:
// Description: 測試串口發送
// 串口發送的數據由內部提供:0 -> A (00001010) -> C (00001100)
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
`define UD #1
module tb_uart_tx();
//==========================================================================
//wire and reg 定義:信號與參數
//==========================================================================
reg sim_clk; //模擬時鐘信號
reg sim_rst_n;
reg tx_pulse; // active posedge
reg [7:0] tx_data; //送入串口發送模塊,準備發送的數據
wire uart_tx; //串口發送信號線
wire tx_busy; //串口發送模塊狀態
//時鐘參數
parameter SYS_CLK_FRE = 40_000_000; //系統頻率40MHz 40_000_000
parameter SYS_CLK_PERIOD = 1_000_000_000/SYS_CLK_FRE; //週期25ns
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
//模擬復位信號:拉低一次
#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; //解除復位
//==========================================================================
//模擬串口發送:並行數據,串行輸出
//==========================================================================
tx_pulse = 0;
tx_data = 8'd0;
repeat( BPS_NUM*1 ) @(posedge sim_clk); //循環347個時鐘週期,即一個波特率週期
//#BAUD_RATE_PERIOD; //直接延時,一個波特率週期
$display("Initialization complete. BAUD_RATE is %d",BAUD_RATE); //命令行顯示初始化完成,輸出BAUD_RATE
//傳遞 第一組數據:8位並行數據,一次性送入串口發送模塊
tx_data = 8'hA; //00001010
#BAUD_RATE_PERIOD;
//開啓觸發信號:串口發送
tx_pulse = 1; //高有效
#BAUD_RATE_PERIOD;
//結束觸發:串口發送
tx_pulse = 0;
#BAUD_RATE_PERIOD;
$display("The first tx_data A (1010) has been sent."); //命令行顯示:第1組數據發送完
//延時 12個 BPS 波特率週期,再發第二組數據
repeat( BPS_NUM*12 ) @(posedge sim_clk);
//傳遞 第二組數據:8位並行數據,一次性送入串口發送模塊
tx_data = 8'hC; //00001100
#BAUD_RATE_PERIOD;
//開啓觸發信號:串口發送
tx_pulse = 1;
#BAUD_RATE_PERIOD;
//結束觸發:串口發送
tx_pulse = 0;
#BAUD_RATE_PERIOD;
$display("The second tx_data C (1100) has been sent."); //命令行顯示:第2組數據發送完
repeat( BPS_NUM*5 ) @(posedge sim_clk);
$stop; //結束仿真
end
//==========================================================================
//調用top模塊
//==========================================================================
//串口發送
uart_tx #(
//.CLK_SYS ( SYS_CLK_FRE), //系統時鐘
.BPS_NUM ( BPS_NUM ) // 時鐘/波特率,用時鐘週期構造波特率週期
)
u_uart_tx(
.clk ( sim_clk ),// input clk,
.tx_data ( tx_data ),// input [7:0] tx_data,
.tx_pulse ( tx_pulse ),// input 外部輸入,開始產生數據->開啓串口發送狀態
.uart_tx ( uart_tx ),// output reg uart_tx,
.tx_busy ( tx_busy ) // output 輸出,串口接收忙狀態
);
endmodule
3、ModelSim仿真結果
第一組數據 8’hA -> 00001010
第二組數據 8’Hc -> 00001100
命令行顯示結果