串口发送的数据:0 -> A (1010) -> C (1100) ->0
时钟40MHz,波特率115200
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
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
第一组数据 8’hA -> 00001010
第二组数据 8’Hc -> 00001100
命令行显示结果