16字节协议的串口通信

1.协议要求

协议为帧传输,一共16字节。主要是2字节的固定帧头 EB 90,2字节的帧计数(用来计数发出的帧),10字节的数据和2字节的校验位

帧头:2字节,固定值 8'HEB、8'H90

帧计数:2字节,用来说明发出去帧是第几帧

数据:10字节,用于发送数据

校验位:2字节,和校验,将数据位累计后取反

2.架构设计

2.1整体架构设计

本架构基于黑金开发版Ax7101,开发工具vivado2022.3,整个协议的架构图设计如下:

整个架构的功能是实现帧的接收和帧的发送,该架构可以分成两个部分:解帧使用组帧发送的两个部分。

数据流向解析

  • 组帧发送部分

    晶振时钟(200Mhz)和板上复位信号先流入锁相环PLL,得到的是50Mhz的时钟(因为200Mzh时钟经PLL分频不能一次性分频到波特率时钟如9600,因此需要再次分频)。

    50Mhz的时钟先流入分频模块clk_div 将分频得到波特率率时钟的16倍时钟(如9600*16,这里之所以不用波特率时钟,是因为要在单字节接收模块uart_rx用过采样,为了节省资源所有的时钟都用这个波特时钟的16时钟,本文称之为over_clk)

    经过分频后的over_clk 流入所有模块作为输入时钟,同时pll_lock信号作为复位信号sys_rst_n.(这里用plll_lock作为复位信号是因为,在pll_lock为低电平的时候,输出时钟是不稳定的,是有毛刺的。而当时pll_lock输入为高电平的时候才是输出的稳定时钟over_clk)

    接着来到了主要功能实现模块,首先是用户会给你一个数据,让你把这个数据发出去,这里假设给你一帧数据(10字节),如果10字节每个字节是一样的,如都是8'H01。那么就是用uart_tx发送10次单字节模块就好了。然后,用户为了保证传输安全,他给你规定了一个协议(2byte帧头+2byte帧计数+10byte数据+2byte校验)。因此,你拿到数据,首先应该做的是先封装成一帧,然后再去想怎么将这一帧发出去的问题。

    这就有了组帧模块Frame ,通过Frame 模块将用户输入的10字节数据封装成一帧,为了隔离组帧模块和单字节发送模块Tx_Drive ,所以在这两个模块之间加了异步FIFO(FIFO_Frame ),同时也是为了平衡组帧和发送这一帧的速率。因为组帧模块组帧完成后,输入的FIFO_Frame的是连续的16个字节,而发送模块是一字节一字节的发送的,两模块之间的速度存在差异。

    而组帧模块Frame所做的就是将组帧好的16字节数据送入FIFO_Frame ,然后继续组帧,继续送入...就这样一直工作。而Tx_Drive模块的功能是先从FIFO_Frame 取出1字节,然后将1字节的数据通过串口tx发送出去。然后再到FIFO_Frame里面去取,再发...就这样就这实现了整个组帧发送部分。

  • 解帧使用部分

    首先输入端口是由tx发出的二进制数据流,所以先需要Rx_Drive模块将这个数据转换成16字节的8位数据,而在收到16字节之后,这16字节中只有10字节的数据,而用户需要只是这10字节的数据,而不是带帧格式的整个帧,所以需要解帧模块Unframe模块将16字节的模块中10字节数据提取出来。而中间的流动过程跟组帧模块相反,但思想几乎一模一样,唯一区别就是解帧模块Unframe要在Rx_Drive一字节一字节存入的FIFO_Unframe中一次性取出16字节,然后从这16字节中把数据取出来。

2.1.1组帧发送

主要构成:Frame(组帧模块)+FIFO_Frame(同步FIFO)+Tx_Drive(串口发送模块)

**主要功能:**将用户给的数据,先组帧,然后通过单字节串口将数据一字节一字节的方式发出去。

2.1.2解帧使用

主要构成:Unframe(解帧模块)+FIFO_Unframe(同步FIFO)+Rx_Drive(串口发送模块)

主要功能:将收到数据流解析成16字节,然后解帧模块再将其中的数据取出来,供用户使用。

2.2 定时发送架构设计

定时发送模块是属于上面整体架构的组帧发送部分,因为组帧发送需要出入10字节的数据,才能将数据组帧,然后发送出去,所以需要一个定时产生数据的模块。

3.模块代码分析

本章叙述流程,将会先从从定时发送发送多字节模块(即多了定时发送的组帧发送模块)展开叙述每个模块的具体细节和实现思路,然后将再将定时发送tx端产生的数据流,流入解帧接收部分,从而一点点随着数据的流动,逐渐由表及里,剖析代码。

3.1 定时模块 Timeout_Send

定时发送模块Timeout_Send为顶层模块,该模块是下面两个功能的封装:

(1)定时

(2)产生一帧要发送的数据(10字节)

参数控制

  • P_SYS_CLK :输入时钟,默认是9600*16
  • P_SEND_BYTE_LENGTH:发送一帧长度,自动计算需要产生多少字节数据
  • P_TIMEOUT_PRIOD:发送间隔,默认5ms发送一帧

框图

参考代码

verilog 复制代码
`timescale 1ns / 1ps
// 定时发送模块,到时间后输出高电平

module Timeout_Send
#(
    parameter P_SYS_CLK             = 153_600   ,
    parameter P_SEND_BYTE_LENGTH    = 16       ,
    parameter P_TIMEOUT_PRIOD       = 500        //定时发送1帧数据,单位毫秒,默认5ms
)
(
    input           i_clk       ,             //时钟为50Mhz
    input           i_rst_n     ,             //工作使能信号
    input           i_full      ,

    output  [7:0]   o_data      ,             //输出的数据
    output          o_valid                   //输出数据的有效信号
);

wire                work_en;

Set_Timeout
#(
    .P_SYS_CLK      (P_SYS_CLK      )   ,
    .P_TIMEOUT_PRIOD(P_TIMEOUT_PRIOD)       //定时发送1帧数据,单位毫秒,默认50ms
)
Set_Timeout_U0
(
    .i_clk          (i_clk          )   ,   //时钟为50Mhz
    .i_rst_n        (i_rst_n        )   ,

    .o_timeout      (work_en        )       //时间到了就拉高一个周期
);

Product_Data
#(
    .P_SEND_BYTE_LENGTH (P_SEND_BYTE_LENGTH)
)
Product_Data_U0
(
    .i_clk          (i_clk          )   ,
    .i_rst_n        (i_rst_n        )   ,
    .i_work_en      (work_en        )   ,
    .i_full         (i_full         )   ,

    .o_data         (o_data         )   ,
    .o_valid        (o_valid        )  
);
endmodule
3.1.1 定时模块Set_Timeout

主要功能:设置的定时时间到了,就拉高一个工作使能信号o_timeout,默认5ms拉高信号。

参数控制

  • P_SYS_CLK:输入时钟,默认分频时钟的16倍,每个周期是10^6/(9600*16)=6.51us
  • P_TIMEOUT_PRIOD:发送间隔,定时发送1帧数据,单位毫秒,默认5ms

参考代码:

verilog 复制代码
`timescale 1ns / 1ns
module Set_Timeout
#(
    parameter P_SYS_CLK         = 153_600,  //分频时钟的16倍,每个周期是10^6/(9600*16)=6.51us
    parameter P_TIMEOUT_PRIOD   = 500       //定时发送1帧数据,单位毫秒,默认5ms
)
(
    input   i_clk       ,                   //时钟为50Mhz
    input   i_rst_n     ,

    output  o_timeout                       //时间到了就拉高一个周期
);

/***************parameter*************/
localparam      P_CNT_MAX       = P_TIMEOUT_PRIOD*P_SYS_CLK/1000_000; 

reg             ro_timeout              ;
reg     [31:0]  r_cnt                   ;

assign          o_timeout = ro_timeout  ;
/***************always****************/
always @(posedge i_clk, negedge i_rst_n) begin
    if(~i_rst_n)
        r_cnt <= 32'd0;
    else if(r_cnt == P_CNT_MAX - 1)
        r_cnt <= 32'd0;
    else
        r_cnt <= r_cnt + 1;
end

always @(posedge i_clk, negedge i_rst_n) begin
    if(~i_rst_n)
        ro_timeout <= 1'b0;
    else if(r_cnt == P_CNT_MAX - 1)
        ro_timeout <= ro_timeout + 1;
    else
        ro_timeout <= ro_timeout;
end
endmodule
3.1.2 数据产生模块Product_Data

主要功能:收到拉高的工作使能信号,就输出一帧数据,默认50ms输出一帧数据。

参数控制

  • P_SEND_BYTE_LENGTH:一帧要发送的字节的长度,自动除去帧头、帧计数和帧校验,发需要发的字节长度。列如16字节:16-2-2-2

参考代码:

verilog 复制代码
`timescale 1ns / 1ps

//产生模拟数据模块:当时工作使能信号为高,同时有握手信号的才输出数据
module Product_Data
#(
    parameter P_SEND_BYTE_LENGTH = 16      
)
(
    input           i_clk       ,
    input           i_rst_n     ,
    input           i_work_en   ,
    input           i_full      ,

    output  [7:0]   o_data      ,
    output          o_valid
);

/***************function**************/
/***************parameter*************/
localparam P_FRAME_DATA_LENGTH = P_SEND_BYTE_LENGTH -2-2-2;//一帧数据的长度
/***************port******************/             
/***************mechine***************/
/***************reg*******************/
reg   [7:0]         ri_data     ;
reg                 ri_valid    ;
reg   [9:0]         valid_cnt   ;
reg                 send_en     ;
reg   [7:0]         r_byte_cnt  ;   //输出字节计数器,用来约束只发一帧数据
reg                 is_finish   ;   //完整输出一帧的后拉高信号,不在产生数据
/***************wire******************/
wire                wo_rd_en    ;
/***************component*************/
localparam          P_VALID_CNT = 10 ;
/***************assign****************/
assign              wo_rd_en = ~i_full  ;
assign              o_data   = ri_data  ;
assign              o_valid  = ri_valid ;
/***************always****************/
always @(posedge i_clk,negedge i_rst_n) begin   //限制发送一帧数据长度计数器
    if(~i_rst_n)
        r_byte_cnt <= 'd0;
    else if(r_byte_cnt == P_FRAME_DATA_LENGTH)
        r_byte_cnt <= 'd0;
    else if(ri_valid && ~is_finish)
        r_byte_cnt <= r_byte_cnt + 1;
    else
        r_byte_cnt <= r_byte_cnt;
end
always @(posedge i_clk ,negedge i_rst_n) begin
    if(~i_rst_n)
        is_finish <= 'd0;
    else if(~i_work_en)
        is_finish <= 'd0;
    else if(r_byte_cnt == P_FRAME_DATA_LENGTH)
        is_finish <= 'd1;
    else
        is_finish <= is_finish;
end
always@(posedge i_clk,negedge i_rst_n)begin //发送使能,当拉高的时候,处于发数据阶段
    if(~i_rst_n)
        send_en <= 'd0;
    else if(r_byte_cnt == P_FRAME_DATA_LENGTH && valid_cnt == P_VALID_CNT-1)
        send_en <= 'd0;
    else if(valid_cnt == P_VALID_CNT-1)
        send_en <= 'd0;
    else if(wo_rd_en && i_work_en && is_finish==0)
        send_en <= 'd1;
    else
        send_en <= send_en;
end
always @(posedge i_clk,negedge i_rst_n) begin
    if(~i_rst_n)
        valid_cnt <= 'd0;
    else if(valid_cnt == P_VALID_CNT)
        valid_cnt <= 'd0;
    else if(wo_rd_en  && i_work_en && ~is_finish)
        valid_cnt <= valid_cnt+1;
    else
        valid_cnt <= 'd0;
end
always @(posedge i_clk,negedge i_rst_n) begin
    if(~i_rst_n)
        ri_data <= 'd0;
    else if(~wo_rd_en)
        ri_data <= 'd0;
    else if(~send_en && i_work_en)
        ri_data <= ri_data + 1;
    else
        ri_data <= ri_data;
end
always @(posedge i_clk,negedge i_rst_n) begin
    if(~i_rst_n)
        ri_valid <= 'd0;
    else if(valid_cnt == P_VALID_CNT-1 && ~is_finish)
        ri_valid <= 1'b1;
    else
        ri_valid <= 'd0;
end
endmodule

3.2组帧发送

3.2.1组帧模块Frame

主要功能:将用户输入的数据封装成帧发送出去

参数控制

  • P_CRC_POLY:CRC校验的多项式,默认使用和校验,要用需要解开注释部分。默认CRC-16模型
  • P_FIRST_MARK:帧头第一位,默认为8'HEB。
  • P_SECOND_MARK:帧头第二位,默认为8'H90。
  • P_SEND_BYTE_LENGTH:待发送的一帧长度,默认16字节。

框图:

参考代码

verilog 复制代码
`timescale 100ps / 1ps
/*计算机校验位,组帧发出去*/

module Frame
#(
    parameter       P_CRC_POLY          = 16'H8005  ,    //CRC校验中的多项式
    parameter       P_FIRST_MARK        = 8'HEB     ,
    parameter       P_SECOND_MARK       = 8'H90     ,
    parameter       P_SEND_BYTE_LENGTH  = 16

)
(
    input           i_clk       ,
    input           i_rst_n     ,
    input   [7:0]   i_data      ,
    input           i_valid     ,
    input           i_full      ,   //写入的FIFO是否满
    input           i_empty     ,   //被读入的FIFO是否空,当其拉高的时候读使能不在有效

    output  [7:0]   o_data      ,
    output          o_rd_en     ,             //读使能信号,当fifo非空且需要读数据的拉高
    output          o_valid
);
/***************function**************/
    function    automatic [15:0]  get_CRC;	//生成CRC校验的函数
    input   [15:0]      i_CRC_reg;
    input   [7:0]       i_f_poly;
    integer i;
    begin
        for(i = 0;i<8;i = i+1)begin
            i_CRC_reg = i_CRC_reg >> 1;
            if(i_CRC_reg[0] == 1)
                 i_CRC_reg = i_CRC_reg ^ {1'b1,i_f_poly};
        end
        get_CRC = i_CRC_reg;
    end
endfunction

    function automatic [15:0] get_Sum;	//累加函数,用于生成累加校验checksum
    input   [15:0]      i_sum_reg;
    input   [7:0]       i_sum_item;
    get_Sum  = i_sum_reg + i_sum_item;
endfunction

/***************parameter*************/
localparam          P_VALID_CNT = 3;
/***************port******************/             
/***************mechine***************/
/***************reg*******************/
reg     [7:0]       ro_data             ;
reg                 ro_valid            ;
reg                 ro_valid_1d         ;
reg     [15:0]      r_CRC_Reg           ;       //原始数据左移之后的16位数据
reg     [4:0]       r_byte_cnt          ;       //接收字节计数器
reg     [79:0]      r_recive_container  ;
reg     [4:0]       r_send_byte_cnt     ;       //发送字节计数器
reg                 r_send_lock         ;       //接收的时候为低电平,高电平为发送状态  
reg                 ro_rd_en            ;
reg     [1:0]       r_valid_cnt         ;
reg     [15:0]      r_frame_cnt         ;       //帧计数器
/***************wire******************/
/***************component*************/
/***************assign****************/
assign              o_data  = ro_data       ;
assign              o_valid = ro_valid_1d   ;
assign              o_rd_en = ro_rd_en      ;
/***************always****************/

always @(posedge i_clk,negedge i_rst_n) begin
    if(~i_rst_n)
        r_byte_cnt <= 'd0;
    else if(r_byte_cnt == (P_SEND_BYTE_LENGTH - 4-2))      //去除帧头标志位和校验位
        r_byte_cnt <= 'd0;
    else if(i_valid)
        r_byte_cnt <= r_byte_cnt + 1;
    else
        r_byte_cnt <= r_byte_cnt;
end

always @(posedge i_clk,negedge i_rst_n) begin           //1代表空闲
    if(~i_rst_n)
        ro_rd_en <= 'd1;
    else if(r_byte_cnt == (P_SEND_BYTE_LENGTH - 4-2))     //提前1位拉高,11位的时候拉高,然后valid慢数据1位,所以要提前两位
        ro_rd_en <= 'd0;
    else if(r_send_byte_cnt == P_SEND_BYTE_LENGTH && r_valid_cnt == 0 && ~i_empty)
        ro_rd_en <= 'd1;
    else if(r_byte_cnt <(P_SEND_BYTE_LENGTH - 4-2)&&r_send_byte_cnt == 0 && ~i_empty)
        ro_rd_en <= 'd1;
    else 
        ro_rd_en <= ro_rd_en;
end

always @(posedge i_clk,negedge i_rst_n) begin           //接收的时候为低电平,高电平为发送状态  
    if(~i_rst_n)
        r_send_lock <= 1'b0;
    else if(r_byte_cnt == (P_SEND_BYTE_LENGTH - 4-2) && ~i_full)     //当接受完12为数据,就开始准备发送
        r_send_lock <= 1'b1;
    else if(r_send_byte_cnt == P_SEND_BYTE_LENGTH && r_valid_cnt == 0)      //当发送完,变成接收状态
        r_send_lock <= 1'b0;
    else
        r_send_lock <= r_send_lock;
end

always @(posedge i_clk,negedge i_rst_n) begin
    if(~i_rst_n)
        // r_CRC_Reg <= 16'HFFFF;						//CRC校验的初始化
        r_CRC_Reg <= 'd0;
    else if(r_send_byte_cnt == P_SEND_BYTE_LENGTH  && r_valid_cnt == 0)
        // r_CRC_Reg <= 16'HFFFF;						//CRC校验的归零
        r_CRC_Reg <= 'd0;
   // else if(i_valid) begin                           	//CRC校验的写法:每收到一字节就先把包括这字节及其以前的所有字节全都计算出来,收完就计算完;
   		//r_CRC_Reg <= i_data^r_CRC_Reg[7:0];
    	//r_CRC_Reg <= get_CRC(r_CRC_Reg,P_CRC_POLY);  
   // end
    else if(i_valid) begin                              //checksum校验的写法:先把12收完再计算出校验位
        r_CRC_Reg <= get_Sum(r_CRC_Reg,i_data);
    end
    else if(r_byte_cnt == (P_SEND_BYTE_LENGTH - 4-2))   //加上帧计数一起算
        r_CRC_Reg <= ~r_CRC_Reg;
    else
        r_CRC_Reg <= r_CRC_Reg;
end

always @(posedge i_clk, negedge i_rst_n) begin
    if(~i_rst_n)
        r_valid_cnt <= 'd0;
    else if(r_valid_cnt == P_VALID_CNT)
        r_valid_cnt <= 'd0;
    else if(r_send_lock)
        r_valid_cnt <= r_valid_cnt + 1;
    else
        r_valid_cnt <= 'd0;
end

always@(posedge i_clk, negedge i_rst_n)begin
    if(~i_rst_n)
        r_send_byte_cnt <= 'd0;
    else if(r_send_byte_cnt == P_SEND_BYTE_LENGTH && r_valid_cnt == 0)
        r_send_byte_cnt <= 'd0;
    else if(r_send_lock && r_valid_cnt == 0)
        r_send_byte_cnt <= r_send_byte_cnt+1;
    else
        r_send_byte_cnt <= r_send_byte_cnt;
end

always @(posedge i_clk, negedge i_rst_n) begin
    if(~i_rst_n)
        ro_valid <= 1'b0;
    else if(r_send_lock && r_send_byte_cnt <=P_SEND_BYTE_LENGTH && r_valid_cnt == P_VALID_CNT)
        ro_valid <= 1'b1;
    else
        ro_valid <= 1'b0;
end

always@(posedge i_clk,negedge i_rst_n)begin
    if(~i_rst_n)
        ro_valid_1d <= 1'b0;
    else 
        ro_valid_1d <= ro_valid;
end

always @(posedge i_clk, negedge i_rst_n) begin
    if(~i_rst_n)
        r_recive_container <= 'd0;
    else if(r_send_byte_cnt == P_SEND_BYTE_LENGTH && r_valid_cnt == 0)
        r_recive_container <= 'd0;
    else if(i_valid)
        r_recive_container <= {i_data,r_recive_container[79:8]};
    else if(r_valid_cnt == 2 && r_send_byte_cnt > 4 && r_send_byte_cnt < P_SEND_BYTE_LENGTH-1)
        r_recive_container <= r_recive_container>>8;
    else
        r_recive_container <= r_recive_container;
end

always @(posedge i_clk, negedge i_rst_n) begin
    if(~i_rst_n)
        ro_data <= 'd0;
    else if(r_send_byte_cnt == 0 && r_valid_cnt == 1)
        ro_data <= 'd0;
    else if( r_send_lock && ~i_full && r_send_byte_cnt==1)
        ro_data <= 8'HEB;
    else if( r_send_lock && ~i_full && r_send_byte_cnt==2)
        ro_data <= 8'H90;
    else if( r_send_lock && ~i_full && r_send_byte_cnt==3)
        ro_data <= r_frame_cnt[7:0];
    else if( r_send_lock && ~i_full && r_send_byte_cnt==4)
        ro_data <= r_frame_cnt[15:8];
    else if( r_valid_cnt == 1 && r_send_byte_cnt > 4 && r_send_byte_cnt < P_SEND_BYTE_LENGTH-1) //接收完12字节数据,同时算出CRC校验码/累加和码
        ro_data <= r_recive_container[7:0];
    else if(r_valid_cnt == 1 && r_send_byte_cnt == P_SEND_BYTE_LENGTH - 1)
        ro_data <= r_CRC_Reg[7:0];
    else if(r_valid_cnt == 1 && r_send_byte_cnt== P_SEND_BYTE_LENGTH)
        ro_data <= r_CRC_Reg[15:8];
    else
        ro_data <= ro_data;
end

//帧计数器的赋值
always @(posedge i_clk, negedge i_rst_n) begin
    if(~i_rst_n)
        r_frame_cnt <= 'd0;
    else if(r_byte_cnt == (P_SEND_BYTE_LENGTH - 4-2)) //提前1时钟周期,让最后一个数据接收完的时候,把
        r_frame_cnt <= r_frame_cnt + 1;
    else
        r_frame_cnt <= r_frame_cnt;
end
endmodule
3.2.2 单字节发送的顶层模块Tx_Drive

主要功能:用来控制底层模块uart_tx模块

参数控制

  • P_SYS_CLOCK:输入时钟,默认50Mhz.
  • P_UART_BAUDRATE:波特率,默认9600
  • P_UART_STOP_WIDTH:停止位长度,默认为1
  • P_UART_PARITY_WIDTH:1代表奇校验,2代表偶校验,0代表无校验,默认奇校验
  • P_OVER_SAMPLE:过采样倍数,默认16倍

框图:

参考代码

verilog 复制代码
`timescale 1ns / 1ps

module Tx_Drive
#(
    parameter               P_SYS_CLOCK           =   50_000_000    ,   //系统时钟可配置,默认50MHZ
    parameter               P_UART_BAUDRATE       =   256000        ,   //波特率可配置,默认9600
    parameter               P_UART_STOP_WIDTH     =   1             ,   //停止位宽度,默认1位
    parameter               P_UART_PARITY_WIDTH   =   1             ,   //校验位,默认0,0不校验,1奇校验,2偶校验
    parameter               P_OVER_SAMPLE         =   16                //接收模块的过采样次数
)
(
    input                   i_sys_clk                               ,
    input                   i_rst_n                                 ,
    input                   i_valid                                 ,
    input   [7:0]           i_data                                  ,
    input                   i_empty                                 ,

    output                  o_tx                                    ,
    output                  o_tx_ready

);

localparam                  BAUD_CNT = P_SYS_CLOCK / P_UART_BAUDRATE;

wire                        wo_tx                   ;
wire                        w_uart_tx_ready         ;
reg                         w_tx_drive_ready        ;

assign                      o_tx_ready = w_uart_tx_ready && ~i_empty && w_tx_drive_ready;
assign                      o_tx = wo_tx;
wire                        o_tx_end;

uart_tx
#(
    .P_SYS_CLOCK            (P_SYS_CLOCK        )   ,   //系统时钟可配置,默认50MHZ
    .P_UART_BAUDRATE        (P_UART_BAUDRATE    )   ,   //波特率可配置,默认9600
    .P_UART_DATA_WIDTH      (8                  )   ,   //发送数据位可配置,默认8位
    .P_UART_STOP_WIDTH      (P_UART_STOP_WIDTH  )   ,   //停止位宽度,默认1位
    .P_UART_PARITY_WIDTH    (P_UART_PARITY_WIDTH)       //校验位宽度,0为无校验,1为奇校验,2位偶校验

)
uart_tx_u0
(
    .i_clk                  (i_sys_clk          )   ,
    .i_rst                  (i_rst_n            )   ,
    .i_user_tx_data         (i_data             )   ,   //用户待发送的数据
    .i_user_tx_data_valid   (i_valid            )   ,   //数据发送的有效信号

    .o_uart_tx              (wo_tx              )   ,
    .o_user_tx_ready        (w_uart_tx_ready    )   ,   //数据准备信号,当有效信号和准备信号同时为高的时候,发送的数据才是有效的
    .o_user_tx_end          (o_tx_end           )
);

reg                         o_tx_end_1d;
reg         [3:0]           work_cnt;
localparam                  P_CNT_MAX = 3;

always @(posedge i_sys_clk,negedge i_rst_n) begin
    if(~i_rst_n)
        o_tx_end_1d <= 'd0;
    else
        o_tx_end_1d <= o_tx_end;
end

always @(posedge i_sys_clk,negedge i_rst_n) begin
    if(~i_rst_n)
        work_cnt <= 'd0;
    else if(~w_tx_drive_ready&& ~i_empty&&work_cnt<P_CNT_MAX)
        work_cnt <= work_cnt + 1;
    else
        work_cnt <= work_cnt;
end

always @(posedge i_sys_clk,negedge i_rst_n) begin
    if(~i_rst_n)
        w_tx_drive_ready <= 'd0;
    else if(work_cnt == 2)
        w_tx_drive_ready <= 'd1;                   				//第一次触发
    else if(i_valid)
        w_tx_drive_ready <= 'd0;
    else if(o_tx_end && ~o_tx_end_1d )                           //只检测一次
        w_tx_drive_ready <= 'd1;
    else if(w_tx_drive_ready && ~o_tx_ready&&i_empty)
        w_tx_drive_ready <= 'd1;
    else if(~i_empty)
        w_tx_drive_ready <= 'd0;
    else 
        w_tx_drive_ready <= w_tx_drive_ready;
end
endmodule
3.2.3 单字节发送模块uart_tx

主要功能:输入一个5/6/7/8位数据,将这个数据转换成一个串行数据发送出去。

参数控制:

  • P_SYS_CLOCK:输入时钟,默认50Mhz。
  • P_UART_BAUDRATE:波特率,默认9600。
  • P_UART_DATA_WIDTH:输入数据宽度,默认发送8bit数据。
  • P_UART_STOP_WIDTH:停止位宽度,默认为1.
  • P_UART_PARITY_WIDTH:校验位,默认无校验0,1奇校验,2偶校验。
  • P_OVER_SAMPLE:过采样倍数,默认16倍采样。

状态图

状态图解释 :当模块处于空闲的时候是P_IDLE状态,当输入端数据有限信号拉高的同时本模块空闲的时候(即o_tx_ready为1),此时开始进入发送数据的起始位状态P_START,即bit计数器1。当bit计数器大于1的同时小于等于P_UART_DATA_WIDTH的时候,处于发送数据状态P_DATA。当发送完数据之后,判断是否需要发送校验位,当P_UART_PARITY_WIDTH大于0的时候,就进入校验状态,发送校验位;如果P_UART_PARITY_WIDTH等于0,就直接进入停止状态。最后都经过1bit的时间同时本模块空闲的时候,进入空闲状态;如果经过1bit之后,发现本模块还是处于忙碌状态(即o_tx_ready0)就说要发下一个数据,直接进入起始状态。

参考代码:

verilog 复制代码
`timescale 1ns / 1ns

module uart_tx
#(
    parameter                       P_SYS_CLOCK           =   50_000_000    ,   //系统时钟可配置,默认50MHZ
    parameter                       P_UART_BAUDRATE       =   9600          ,   //波特率可配置,默认9600
    parameter                       P_UART_DATA_WIDTH     =   8             ,   //发送数据位可配置,默认8位
    parameter                       P_UART_STOP_WIDTH     =   1             ,   //停止位宽度,默认1位
    parameter                       P_UART_PARITY_WIDTH   =   0             ,   //校验位宽度,0为无校验,1为奇校验,2位偶校验
    parameter                       P_OVER_SAMPLE         =   16                //接收模块的过采样次数

)
(
    input                           i_clk                                   ,   //输入的时钟是波特率的16倍时钟,所以发出数据要持续16个时钟周期
    input                           i_rst                                   ,
    output                          o_uart_tx                               ,

    input   [P_UART_DATA_WIDTH-1:0] i_user_tx_data                          ,   //用户待发送的数据+校验位+停止位
    input                           i_user_tx_data_valid                    ,   //数据发送有效信号
    output                          o_user_tx_ready                         ,   //数据准备信号,当有效信号和准备信号同时为高的时候,发送的数据才是有效的
    output                          o_user_tx_end
);



/****************************状态机写法*********************************************/
/***************function**************/
/***************parameter*************/
localparam                          P_IDLE    =   4'd0                      ,   //状态机的定义
                                    P_START   =   4'd1                      ,
                                    P_DATA    =   4'd2                      ,
                                    P_PARITY  =   4'd3                      ,
                                    P_STOP    =   4'd4                      ;
/***************port******************/             
/***************mechine***************/
/***************reg*******************/
reg         [3:0]                   ct,nt                                   ;   //状态机的现态和次态
reg                                 ro_uart_tx                              ;
reg                                 ro_user_tx_ready                        ;   
reg         [15:0]                  r_bit_cnt                               ;
reg         [7:0]                   r_clk_cnt                               ;   //计数到16为一个波特率的时钟周期
reg         [P_UART_DATA_WIDTH-1:0] r_tx_data                               ;   //移位寄存器
reg                                 r_parity                                ;
/***************wire******************/
wire                                w_tx_active                             ;
reg                                 o_tx_end                                ;
reg                                 o_tx_end_1d                             ;
/***************component*************/
/***************assign****************/
assign                              o_uart_tx       = ro_uart_tx            ;
assign                              o_user_tx_ready = ro_user_tx_ready      ;
assign                              w_tx_active     = i_user_tx_data_valid & ro_user_tx_ready;  //两个同是为高表示可以开始发送数据
assign                              o_user_tx_end   = o_tx_end_1d           ;
/***************always****************/
always @(posedge i_clk,negedge i_rst) begin     //当激活信号开始的时候,就开始产生技术使能,让时钟计数16个周期为一个波特时钟周期
    if(~i_rst)
        r_clk_cnt <= 'd0;
    else if(r_clk_cnt == P_OVER_SAMPLE-1)
        r_clk_cnt <= 'd0;
    else if(~ro_user_tx_ready)
        r_clk_cnt <= r_clk_cnt + 1;
    else
        r_clk_cnt <= 'd0;

end

/***************状态机****************/
always @(posedge i_clk, negedge i_rst) begin            //状态机第一段:同步时序描述状态转移
    if(!i_rst)
        ct = P_IDLE;
    else
        ct = nt;
end

always@(*) begin                                        //状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律
    case (ct)
        P_IDLE: begin
            if(w_tx_active)
                nt = P_START;
            else
                nt = P_IDLE;
        end
        P_START: begin
            if(r_clk_cnt == P_OVER_SAMPLE-1)   
                nt = P_DATA;
            else
                nt = P_START;
        end
        P_DATA: begin
            if(r_bit_cnt ==  P_UART_DATA_WIDTH && r_clk_cnt == P_OVER_SAMPLE-1 &&P_UART_PARITY_WIDTH > 0)
                nt = P_PARITY;
            else if(r_bit_cnt == P_UART_DATA_WIDTH && r_clk_cnt == P_OVER_SAMPLE-1 && P_UART_PARITY_WIDTH == 0)
                nt = P_STOP;
            else
                nt = P_DATA;
        end
        P_PARITY: begin
            if(r_bit_cnt >= (P_UART_DATA_WIDTH +1) && P_UART_PARITY_WIDTH>0 &&r_clk_cnt == P_OVER_SAMPLE-1)
                nt = P_STOP;
            else
                nt = P_PARITY;
        end
        P_STOP: begin
            if(r_bit_cnt == P_UART_DATA_WIDTH + 1 + P_UART_STOP_WIDTH && P_UART_PARITY_WIDTH>0 && r_clk_cnt == P_OVER_SAMPLE-1)
                nt = P_IDLE;
            else if(r_bit_cnt == P_UART_DATA_WIDTH + P_UART_STOP_WIDTH && !P_UART_PARITY_WIDTH && r_clk_cnt == P_OVER_SAMPLE-1)
                nt = P_IDLE;
            else
                nt = P_STOP;
        end
        default: nt = P_IDLE;
    endcase
end


always @(posedge i_clk, negedge i_rst) begin
    if(~i_rst)
        ro_user_tx_ready <= 'd1;
    else if(w_tx_active)
        ro_user_tx_ready <= 'd0;
    else if(r_bit_cnt == P_UART_DATA_WIDTH+P_UART_STOP_WIDTH && !P_UART_PARITY_WIDTH && r_clk_cnt == P_OVER_SAMPLE-1)              //发送完数据拉高,表示tx模块已经空闲了
        ro_user_tx_ready <= 'd1;
    else if(r_bit_cnt == P_UART_DATA_WIDTH+1+P_UART_STOP_WIDTH && P_UART_PARITY_WIDTH>0 && r_clk_cnt == P_OVER_SAMPLE-1)              //发送完数据拉高,表示tx模块已经空闲了
        ro_user_tx_ready <= 'd1;
    else
        ro_user_tx_ready <= ro_user_tx_ready;
end

always @(posedge i_clk, negedge i_rst) begin
    if(!i_rst)
        r_bit_cnt <= 'd0;
    else if(r_bit_cnt == P_UART_DATA_WIDTH+P_UART_STOP_WIDTH && P_UART_PARITY_WIDTH==0 && r_clk_cnt == P_OVER_SAMPLE-1)
        r_bit_cnt <= 'd0;
    else if(r_bit_cnt == P_UART_DATA_WIDTH+1+P_UART_STOP_WIDTH && P_UART_PARITY_WIDTH>0 && r_clk_cnt == P_OVER_SAMPLE-1)
        r_bit_cnt <= 'd0;
    else if(!ro_user_tx_ready&& r_clk_cnt == P_OVER_SAMPLE-1)
        r_bit_cnt <= r_bit_cnt + 1;
    else
        r_bit_cnt <= r_bit_cnt;
end

always @(posedge i_clk, negedge i_rst) begin
    if(!i_rst)
        r_tx_data <= 'd0;
    else if(w_tx_active)
        r_tx_data <= i_user_tx_data;
    else if(r_bit_cnt>0&&r_bit_cnt<=P_UART_DATA_WIDTH && r_clk_cnt == P_OVER_SAMPLE-2)
        r_tx_data <= r_tx_data >> 1;
    else
        r_tx_data <= r_tx_data;
end

always @(posedge  i_clk,negedge i_rst) begin
    if(~i_rst)
        r_parity <= 1'b0;
    else if(ct == P_STOP)
        r_parity <= 1'b0;
    else if(ct == P_DATA && r_clk_cnt == P_OVER_SAMPLE-1)
        r_parity <= r_parity ^ r_tx_data[0];
    else
        r_parity <= r_parity;
end

always @(posedge i_clk, negedge i_rst) begin            //状态机第三段:时序逻辑描述输出
    if(!i_rst)
        ro_uart_tx <= 1'b1;
    else case(ct)
        P_IDLE  :   ro_uart_tx <= 1'b1;
        P_START :   ro_uart_tx <= 1'b0;
        P_DATA  :   ro_uart_tx <= r_tx_data[0];
        P_PARITY:   ro_uart_tx <= P_UART_PARITY_WIDTH==1?~r_parity:r_parity;
        P_STOP  :   ro_uart_tx <= 1'b1;
        default :   ro_uart_tx <= 1'b1;
    endcase
end

always @(posedge i_clk, negedge i_rst) begin        //结束标志,为高的时候代表空闲
    if(!i_rst)
        o_tx_end <= 1'b1;
    else if(ct == P_START)
        o_tx_end <= 1'b0;
    else if(ct == P_STOP && r_clk_cnt == P_OVER_SAMPLE-2)
        o_tx_end <= 1'b1;
    else
        o_tx_end <= o_tx_end;
end
always @(posedge i_clk, negedge i_rst) begin
    if(!i_rst)
        o_tx_end_1d <= 1'b0;
    else
        o_tx_end_1d <= o_tx_end;
end
endmodule

3.3 解帧使用

2.3.1 单字节接收模块顶层Rx_Drive

主要功能:控制单字节接收模块。

参数控制

  • P_SYS_CLOCK:输入时钟,默认50Mhz。
  • P_UART_BAUDRATE:波特率,默认9600。
  • P_UART_STOP_WIDTH:停止位宽度,默认为1.
  • P_UART_PARITY_WIDTH:校验位,默认无校验0,1奇校验,2偶校验。
  • P_OVER_SAMPLE:过采样倍数,默认16倍采样。

参考代码

verilog 复制代码
`timescale 1ns / 1ns

module Rx_Drive#(
    parameter               P_SYS_CLOCK           =   50_000_000    ,   //系统时钟可配置,默认50MHZ
    parameter               P_UART_BAUDRATE       =   9600          ,   //波特率可配置,默认9600
    parameter               P_UART_STOP_WIDTH     =   1             ,   //停止位宽度,默认1位
    parameter               P_UART_PARITY_WIDTH   =   1             ,   //校验位,默认0,0不校验,1奇校验,2偶校验
    parameter               P_OVER_SAMPLE         =   16                //过采样的时钟的倍率
)
(
    input                   i_clk                                   ,   //输入时钟乃过采样时钟
    input                   i_rst_n                                 ,
    input                   i_full                                  ,   //判断待写入的FIFO是否已满
    input                   i_rx                                    ,   

    output                  o_rx_valid                              ,
    output      [7:0]       o_rx_data
);


//接收数据模块
uart_rx
#(
    .P_SYS_CLOCK            (P_SYS_CLOCK        )    ,   //系统时钟可配置,默认50MHZ
    .P_UART_BAUDRATE        (P_UART_BAUDRATE    )    ,   //波特率可配置,默认9600
    .P_UART_STOP_WIDTH      (P_UART_STOP_WIDTH  )    ,   //停止位宽度,默认1位
    .P_UART_PARITY_WIDTH    (P_UART_PARITY_WIDTH)    ,   //校验位,默认0,
    .P_OVER_SAMPLE          (P_OVER_SAMPLE      )        //过采样的时钟的倍率
)
uart_rx_u0 
(
    .i_over_clk             (i_clk              )    ,   //过采样时钟
    .i_rst_n                (i_rst_n            )    ,
    .i_rx                   (i_rx               )    ,   //判断待写入的FIFO是否已满
    /***********************/
    .sys_clk                (sys_clk            )   ,
    /***********************/
    .o_rx_valid             (o_rx_valid         )    ,
    .o_rx_data              (o_rx_data          )
);
endmodule
3.3.2 单字节接收模块uart_rx

主要功能:将输入的5/6/7/8并行数据,串行的发送出去

参数控制

  • P_SYS_CLOCK:输入时钟,默认50Mhz。
  • P_UART_BAUDRATE:波特率,默认9600。
  • P_UART_DATA_WIDTH:输入数据宽度,默认发送8bit数据。
  • P_UART_STOP_WIDTH:停止位宽度,默认为1.
  • P_UART_PARITY_WIDTH:校验位,默认无校验0,1奇校验,2偶校验。
  • P_OVER_SAMPLE:过采样倍数,默认16倍采样。

状态图

状态图解释:开始的时候处于空闲状态P_IDLE,当检测起始位,进入起始位状态P_START,这个状态持续到起始位结束,即bit==0。当bit>0且小于等于P_UART_DATA_WIDTH时候,进入接受数据状态P_DATA,接收完了。根据P_UART_PARITY_WIDTH是否大于0,判断是否进入校验状态P_PARITY。如果P_UART_PARITY_WIDTH大于0,就进入校验状态P_PARITY,然后经过1bit时间,就进入停止状态,即对应停止位P_STOP.否则就直接进入停止状态,然后经过P_UART_STOP_WIDTH个bit时间长度进入P_IDLE.之所以没有直接接入起始位,是因为在发送端设计的时候会一定有空闲状态产生,具体可以看发送端握手信号o_tx_ready。

参考代码:两种实现方式,计数器和状态机,都能用,用的时候只需要注释另一个。

verilog 复制代码
`timescale 1ns / 1ps
/*本模块采用的全在中间采样,包括起始位的判断也是*/

module uart_rx
#(
    parameter               P_SYS_CLOCK             =   50_000_000  ,   //系统时钟可配置,默认50MHZ
    parameter               P_UART_BAUDRATE         =   9600        ,   //波特率可配置,默认9600
    parameter               P_UART_PARITY_WIDTH     =   1           ,   //校验位,默认0,
    parameter               P_UART_STOP_WIDTH       =   1           ,   //停止位宽度,默认1位
    parameter               P_OVER_SAMPLE           =   16              //过采样的时钟的倍率
)   
(
    input                   i_over_clk                              ,   //过采样时钟
    input                   i_rst_n                                 ,
    input                   i_rx                                    ,   //判断待写入的FIFO是否已满
    output                  o_rx_valid                              ,
    output      [7:0]       o_rx_data
);

/****************************计数器写法*********************************************/
localparam                  P_OVER_CHECK_MAX = P_OVER_SAMPLE/2; //控制采样到什么位置停,范围在P_OVER_SAMPL/2-P_OVER_SAMPLE
/***************function**************/
/***************parameter*************/
/***************port******************/
/***************mechine***************/
/***************reg*******************/
reg         [2:0]               r_rx_reg        ;   //延迟打拍
reg                             ri_rx_1d        ;   //打拍
reg                             ri_rx_2d        ;   //打拍
reg                             r_over_en       ;
reg         [7:0]               r_over_cnt      ;   //过采样计数器
reg                             r_start_flag    ;   //检测是否是起始位的信号
reg                             r_bit_en        ;       
reg         [7:0]               r_bit_cnt       ;
reg                             r_get_flag      ;   //数据的过采样点
reg         [7:0]               r_data_container;   //装数据的移位寄存器
reg                             r_check         ;   //生成奇偶校验

reg         [7:0]               ro_rx_data      ;
reg                             ro_rx_valid     ;

/***************wire******************/
/***************component*************/
/***************assign****************/
assign                          o_rx_valid = ro_rx_valid;
assign                          o_rx_data = ro_rx_data  ;
/***************always****************/
always @(posedge i_over_clk,negedge i_rst_n) begin  //打拍
    if(!i_rst_n) begin
        r_rx_reg <= 3'b111;
        ri_rx_1d <= 'd0;
        ri_rx_2d <= 'd0;
    end
    else begin
        r_rx_reg <= {r_rx_reg[1:0],i_rx};
        ri_rx_1d <= r_rx_reg[2];
        ri_rx_2d <= ri_rx_1d;
    end
end

always @(posedge i_over_clk,negedge i_rst_n) begin
    if(!i_rst_n)
        r_over_en <= 'd0;
    else if(r_bit_cnt == 8+1+P_UART_STOP_WIDTH && r_over_cnt == P_OVER_SAMPLE-1 && P_UART_PARITY_WIDTH>0)
        r_over_en <= 'd0;
    else if(r_bit_cnt == 8+P_UART_STOP_WIDTH && r_over_cnt == P_OVER_SAMPLE-1 && P_UART_PARITY_WIDTH==0)
        r_over_en <= 'd0;
    else if(r_bit_cnt == 0 && r_over_cnt == P_OVER_CHECK_MAX+1&& r_bit_en==0)   //异常归零
        r_over_en <= 'd0;
    else if(r_rx_reg[2]==0 && ri_rx_1d)
        r_over_en <= 'd1;
    else
        r_over_en <= r_over_en;
end

always @(posedge i_over_clk,negedge i_rst_n) begin
    if(!i_rst_n)
        r_over_cnt <= 'd0;
    else if(r_over_cnt == P_OVER_SAMPLE -1)
        r_over_cnt <= 'd0;
    else if(r_bit_cnt == 0 && r_over_cnt == P_OVER_CHECK_MAX+1&& r_bit_en==0)   //异常归零
        r_over_cnt <= 'd0;
    else if(r_over_en)
        r_over_cnt <= r_over_cnt + 1;
    else
        r_over_cnt <= r_over_cnt;
end


always @(posedge i_over_clk,negedge i_rst_n) begin
    if(!i_rst_n)
        r_start_flag <= 'd0;
    else if(r_bit_cnt==0 && r_over_cnt == 8'd7 && ri_rx_1d==0 && r_bit_en==0)
        r_start_flag <= 'd1;
    else
        r_start_flag <= 'd0;
end


always @(posedge i_over_clk,negedge i_rst_n) begin
    if(!i_rst_n)
        r_bit_en <= 'd0;
    else if(r_bit_cnt == 8+1+P_UART_STOP_WIDTH && r_over_cnt == P_OVER_SAMPLE-1 && P_UART_PARITY_WIDTH>0)
        r_bit_en <= 'd0;
    else if(r_bit_cnt == 8+P_UART_STOP_WIDTH && r_over_cnt == P_OVER_SAMPLE-1 && P_UART_PARITY_WIDTH==0)
        r_bit_en <= 'd0;
    else if(r_start_flag)
        r_bit_en <= 'd1;
    else
        r_bit_en <= r_bit_en;
end
    
always @(posedge i_over_clk,negedge i_rst_n) begin
    if(!i_rst_n)
        r_bit_cnt <= 'd0;
    else if(r_bit_cnt == 8+1+P_UART_STOP_WIDTH && P_UART_STOP_WIDTH>0 && r_over_cnt == P_OVER_SAMPLE-1)
        r_bit_cnt <= 'd0;
    else if(r_bit_cnt == 8+P_UART_STOP_WIDTH && P_UART_STOP_WIDTH==0 && r_over_cnt == P_OVER_SAMPLE-1)
        r_bit_cnt <= 'd0;
    else if(r_bit_en && r_over_cnt == P_OVER_SAMPLE-1)
        r_bit_cnt <= r_bit_cnt + 1;
    else
        r_bit_cnt <= r_bit_cnt;
end

always @(posedge i_over_clk,negedge i_rst_n) begin
    if(!i_rst_n)
        r_get_flag <= 'd0;
    else if(r_bit_cnt >8'd0 && r_bit_cnt <8'd9 && r_over_cnt == P_OVER_CHECK_MAX-1)
        r_get_flag <= 'd1;
    else
        r_get_flag <= 'd0;
end

always @(posedge i_over_clk,negedge i_rst_n) begin
    if(!i_rst_n)
        r_data_container <= 'd0;
    else if(r_bit_cnt == 8+1+P_UART_STOP_WIDTH && P_UART_STOP_WIDTH>0 && r_over_cnt == P_OVER_SAMPLE-1)
        r_data_container <= 'd0;
    else if(r_bit_cnt == 8+P_UART_STOP_WIDTH && P_UART_STOP_WIDTH==0 && r_over_cnt == P_OVER_SAMPLE-1)
        r_data_container <= 'd0;
    else if(r_get_flag)
        r_data_container <= {ri_rx_2d,r_data_container[7:1]};
    else
        r_data_container <= r_data_container;

end

always @(posedge i_over_clk,negedge i_rst_n) begin
    if(!i_rst_n)
        r_check <= 'd0;
     else if(r_bit_cnt == 8+1+P_UART_STOP_WIDTH && P_UART_STOP_WIDTH>0 && r_over_cnt == P_OVER_SAMPLE-1)
        r_check <= 'd0;
    else if(r_bit_cnt == 8+P_UART_STOP_WIDTH && P_UART_STOP_WIDTH==0 && r_over_cnt == P_OVER_SAMPLE-1)
        r_check <= 'd0;
    else if(r_get_flag)
        r_check <= ri_rx_2d^r_check;
    else if(r_bit_cnt == 8'd9 && P_UART_PARITY_WIDTH == 1&& r_over_cnt == 0)  //在数据位后面时候就生成校验值,偶校验直接是异或值
        r_check <= !r_check;
    else
        r_check <= r_check;
end

always @(posedge i_over_clk,negedge i_rst_n) begin
    if(!i_rst_n)
        ro_rx_data <= 'd0;
    else if(r_bit_cnt == 9)    //直接输出
        ro_rx_data <= r_data_container;
    else
        ro_rx_data <= ro_rx_data;

end

always @(posedge i_over_clk,negedge i_rst_n) begin
    if(!i_rst_n)
        ro_rx_valid <= 'd0;
    else if(r_bit_cnt == 8'd9 && P_UART_PARITY_WIDTH>0 && r_over_cnt == 1)
        ro_rx_valid <= r_check == ri_rx_2d?1:0;
    else if(r_bit_cnt == 8'd9 && P_UART_PARITY_WIDTH == 0 && r_over_cnt == 1)
        ro_rx_valid <= 'd1; 
    else
        ro_rx_valid <= 'd0;
end

/****************************状态机写法*********************************************/
// /***************function**************/
// localparam                  P_OVER_CHECK_MAX = P_OVER_SAMPLE/2; //控制采样到什么位置停,范围在P_OVER_SAMPL/2-P_OVER_SAMPLE

// /***************parameter*************/
// localparam                  P_IDLE   = 4'd0 ,
//                             P_START  = 4'd1 ,
//                             P_DATA   = 4'd2 ,
//                             P_PARITY = 4'd3 ,
//                             P_STOP   = 4'd4 ,
//                             P_ERROR  = 4'd5 ;


// /***************port******************/          
// /***************mechine***************/
// /***************reg*******************/
// reg                         ro_rx_valid     ;   //输出数据有效信号的寄存器
// reg         [7:0]           ro_rx_data      ;   //输出数据

// reg                         ri_rx_d1        ;   //消除亚稳态
// reg                         ri_rx_d2        ;
// reg                         ri_rx_d3        ;

// reg                         ri_rx_1d        ;   //将输入数据延迟一拍
// reg                         ri_rx_2d        ;   //将输入数据再延迟一拍
// reg                         r_cnt_en        ;   //开始计数的使能信号
// reg         [7:0]           r_over_cnt      ;   //超采样计数器
// reg                         r_start_flag    ;   //在第一个低电平的中点点,采样,从而判断起始位
// reg                         r_bit_en        ;   //当检测起始信号之后,就开始拉高bit计数器的使能
// reg         [7:0]           r_bit_cnt       ;   //bit计数器
// reg                         r_get_flag      ;   //采样数据的采样信号
// reg                         r_check         ;   //进行奇偶校验的
// reg         [7:0]           r_data_container;   //数据移位寄存器

// reg         [3:0]           ct,nt       ;   //保存状态的状态机

// /***************wire******************/
// /***************component*************/
// /***************assign****************/
// assign                      o_rx_valid = ro_rx_valid;
// assign                      o_rx_data  = ro_rx_data ;
// /***************always****************/
// always @(posedge i_over_clk,negedge i_rst_n) begin  //延迟信号
//     if(!i_rst_n) begin
//         ri_rx_1d <= 'd0;
//         ri_rx_2d <= 'd0;

//         ri_rx_d1 <= 'd0;
//         ri_rx_d2 <= 'd0;
//         ri_rx_d3 <= 'd0;
//     end
//     else begin
//         ri_rx_1d <= ri_rx_d3;
//         ri_rx_2d <= ri_rx_1d;

//         ri_rx_d1 <= i_rx;
//         ri_rx_d2 <= ri_rx_d1;
//         ri_rx_d3 <= ri_rx_d2;
//     end
// end

// always @(posedge i_over_clk,negedge i_rst_n) begin  //当检测到下降沿的时候开始计数的使能信号
//     if(!i_rst_n)
//         r_cnt_en <= 'd0;
//     else if(ct == P_STOP && r_over_cnt == P_OVER_SAMPLE-1)   //当停止状态的时候归零
//         r_cnt_en <= 'd0;
//     else if(ri_rx_1d && !ri_rx_d3)
//         r_cnt_en <= 'd1;
//     else
//         r_cnt_en <= r_cnt_en;

// end
// always @(posedge i_over_clk,negedge i_rst_n) begin  //超采样时钟
//     if(!i_rst_n)
//         r_over_cnt <= 'd0;
//     else if(r_over_cnt == P_OVER_SAMPLE-1)  //正常归零
//         r_over_cnt <= 'd0;
//     else if(ct == P_IDLE && r_over_cnt == P_OVER_CHECK_MAX +2 && !r_bit_cnt )   //异常归零,即开始计数后,在1bit的时间内发现这个低电平不是起始信号
//         r_over_cnt <= 'd0;
//     else if(r_cnt_en)
//         r_over_cnt <= r_over_cnt + 8'd1;
//     else
//         r_over_cnt <= r_over_cnt;
// end

// always @(posedge i_over_clk,negedge i_rst_n) begin  //检测是否是起始位的信号
//     if(!i_rst_n)
//         r_start_flag <= 'd0;
//     else if(ct == P_IDLE && r_over_cnt == P_OVER_CHECK_MAX-1 && ri_rx_1d == 0)   //在计数到中点(7)的时候,就进行检查是否是起始位
//         r_start_flag <= 'd1;
//     else
//         r_start_flag <= 'd0;
// end

// always @(posedge i_over_clk,negedge i_rst_n) begin  //开始比特计数器的使能信号
//     if(!i_rst_n)
//         r_bit_en <= 'd0;
//     else if(ct == P_STOP && r_over_cnt == P_OVER_SAMPLE-1)
//         r_bit_en <= 'd0;
//     else if(r_start_flag)
//         r_bit_en <= 'd1;
//     else
//         r_bit_en <= r_bit_en;
// end
// always @(posedge i_over_clk,negedge i_rst_n) begin  //比特计数器
//     if(!i_rst_n)
//         r_bit_cnt <= 'd0;
//     else if(ct == P_STOP && r_over_cnt == P_OVER_SAMPLE-1)  //无校验和校验正确的时,归零
//         r_bit_cnt <= 'd0;
//     else if(ct == P_ERROR && r_over_cnt == P_OVER_SAMPLE-1)  //校验错误时,也归零
//         r_bit_cnt <= 'd0;
//     else if(r_bit_en && r_over_cnt == P_OVER_SAMPLE-1)
//         r_bit_cnt <= r_bit_cnt + 8'd1;
//     else
//         r_bit_cnt <= r_bit_cnt;
// end

// always @(posedge i_over_clk,negedge i_rst_n) begin  //中间采样信号,应该在中点的前一时刻
//     if(!i_rst_n)
//         r_get_flag <= 'd0;
//     else if(ct == P_DATA && r_over_cnt == P_OVER_CHECK_MAX-2)
//         r_get_flag <= 'd1;
//     else
//         r_get_flag <= 'd0;
// end

// always @(posedge i_over_clk,negedge i_rst_n) begin  //累计异或,从而算出校验位,跟传输校验位相比
//     if(!i_rst_n)
//         r_check <= 'd0;
//     else if(ct == P_STOP || ct == P_ERROR)
//         r_check <= 'd0;
//     else if(r_get_flag)
//         r_check <= r_check^ri_rx_2d;
//     else
//         r_check <= r_check;
// end
// always @(posedge i_over_clk,negedge i_rst_n) begin  //采样8位数据
//     if(!i_rst_n)
//         r_data_container <= 'd0;
//     else if(ct == P_STOP && r_over_cnt == P_OVER_SAMPLE-1)
//         r_data_container <= 'd0;
//     else if(ct == P_DATA && r_get_flag)
//         r_data_container <= {ri_rx_2d,r_data_container[7:1]};
//     else
//         r_data_container <= r_data_container;
// end


// //状态机第一段:时序逻辑描述现态和次态转移
// always @(posedge i_over_clk,negedge i_rst_n) begin
//     if(!i_rst_n)
//         ct = P_IDLE;
//     else
//         ct = nt;
// end
// //状态机第二段:组合逻辑描述状态转移规律
// always@(*)begin
//     case (ct)
//         P_IDLE:begin
//             if(r_start_flag)
//                 nt = P_START;
//             else
//                 nt = P_IDLE;
//         end
//         P_START:begin
//             if(r_bit_cnt>0 && r_bit_cnt < 8'd9) //数据位八位
//                 nt = P_DATA;
//             else
//                 nt = P_START;
//         end
//         P_DATA:begin
//             if(r_bit_cnt > 8'd8 && P_UART_PARITY_WIDTH == 0)   //无校验位
//                 nt = P_STOP;
//             else if(r_bit_cnt > 8'd8 && P_UART_PARITY_WIDTH > 0)   //无校验位
//                 nt = P_PARITY;
//             else
//                 nt = P_DATA;
//         end
//         P_PARITY:begin
//             if((r_bit_cnt > 8'd9 && ~r_check == ri_rx_2d && P_UART_PARITY_WIDTH == 1)||(r_bit_cnt > 8'd9 && r_check == ri_rx_2d && P_UART_PARITY_WIDTH == 2))
//                 nt = P_STOP;
//             else if((r_bit_cnt > 8'd9 && ~r_check != ri_rx_2d && P_UART_PARITY_WIDTH == 1)||(r_bit_cnt > 8'd9 && r_check != ri_rx_2d && P_UART_PARITY_WIDTH == 2))
//                 nt = P_ERROR;
//             else
//                 nt = P_PARITY;
//         end
//         P_STOP:begin
//             if(P_UART_PARITY_WIDTH>0 && r_bit_cnt == 8+1+P_UART_STOP_WIDTH && r_over_cnt == P_OVER_SAMPLE-1)    //有校验位的情况
//                 nt = P_IDLE;
//             else if(P_UART_PARITY_WIDTH==0 && r_bit_cnt == 8+P_UART_STOP_WIDTH && r_over_cnt == P_OVER_SAMPLE-1)    //无校验位的情况
//                 nt = P_IDLE;
//             else
//                 nt = P_STOP;
//         end
//         P_ERROR:begin
//             if(r_bit_cnt == 8+1+P_UART_STOP_WIDTH && r_over_cnt == P_OVER_SAMPLE-1) //进入错误状态,肯定有校验位
//                 nt = P_IDLE;
//             else
//                 nt = P_STOP;
//         end
//         default:nt = P_IDLE; 
//     endcase
// end
// //状态机第三段:时序逻辑描述输出
// always @(posedge i_over_clk,negedge i_rst_n) begin  //输出数据
//     if(!i_rst_n)
//         ro_rx_data <= 'd0;
//     else if(ct == P_PARITY && P_UART_PARITY_WIDTH >0)   //有校验
//         ro_rx_data <= r_data_container;
//     else if(ct == P_STOP && P_UART_PARITY_WIDTH == 0)   //无校验
//         ro_rx_data <= r_data_container;
//     else
//         ro_rx_data <= ro_rx_data;
// end
// always @(posedge i_over_clk,negedge i_rst_n) begin  //输出数据的有效信号,只拉高一个时钟周期
//     if(!i_rst_n)
//         ro_rx_valid <= 'd0;
//     else if(ct == P_PARITY && ~r_check == ri_rx_2d && P_UART_PARITY_WIDTH == 1 && r_over_cnt == P_OVER_CHECK_MAX-1) //奇校验
//         ro_rx_valid <= 'd1;
//     else if(ct == P_PARITY && r_check == ri_rx_2d && P_UART_PARITY_WIDTH == 2 && r_over_cnt == P_OVER_CHECK_MAX-1)  //偶校验
//         ro_rx_valid <= 'd1;
//     else if(ct == P_STOP && P_UART_PARITY_WIDTH == 0 && r_over_cnt == P_OVER_CHECK_MAX-1)   //无校验
//         ro_rx_valid <= 'd1;
//     else
//         ro_rx_valid <= 'd0;
// end
endmodule
3.3.3 解帧模块Unframe

主要功能:从某个容器读到帧头标志位8'HEB开始,连续读P_SEND_BYTE_LENGTH个字节,然后从中读出10个字节的数据

参数控制

  • P_CRC_POLY:CRC校验多项式,默认CRC-16模型
  • P_FIRST_MARK:帧头第一个标志位
  • P_SECOND_MARK:帧头第二标志位
  • P_SEND_BYTE_LENGTH:发送字节

参考代码

verilog 复制代码
`timescale 1ns / 1ns

module Unframe
#(
    parameter       P_CRC_POLY          = 16'H8005  ,   //CRC校验中的多项式
    parameter       P_FIRST_MARK        = 8'HEB     ,
    parameter       P_SECOND_MARK       = 8'H90     ,
    parameter       P_SEND_BYTE_LENGTH  = 16
)
(
    input           i_clk   ,
    input           i_rst_n ,
    input           i_full  ,   //判断解帧后的FIFO是否满
    input           i_empty ,   //在读入的时候,判断FIFO是否为空
    input   [7:0]   i_data  ,   //输入数据跟数据有效信号持续时间是不同的,默认是数据之间是有间隔的
    input           i_valid ,
    
    output  [7:0]   o_data  ,   
    output          o_valid ,
    output          o_rd_en
);

/***************function**************/
/***************parameter*************/
/***************port******************/
/***************mechine***************/
/***************reg*******************/
reg [7:0]           ro_data             ;
reg                 ro_valid            ;
reg                 ri_valid_1d         ;   //将有效信号延后一时钟周期,以便后面采集数据的时候只采集一次
reg                 ro_rd_en            ;
reg [7:0]           ri_data_1d          ;   //将数据打一拍,便于采集数据
reg [1:0]           r_is_mark_cnt       ;   //用累加计数的方式判断是否是帧头,帧头的第一二字节都符合才开始解帧,不是帧头则继续读,直到读出的是帧头为止
reg [1:0]           r_is_mark_cnt_1d    ;   //延迟一排用来判断上升沿
reg [7:0]           r_byte_cnt          ;   //字节计数器
reg                 r_unframe_en        ;   //解帧的使能,只有拉高的时候才开始解帧,即帧头匹配成功的时候开始解帧
reg [15:0]          r_frame_counter     ;   //帧计数计数器
reg [15:0]          r_frame_container   ;   //从数据流上取出帧计数
reg                 r_frame_is_True     ;   //检验帧计数是否正确的标志信号
reg [79:0]          r_frame_data        ;   //用来装十字节数据的寄存器
reg [15:0]          r_checksum_container;   //装接收帧的两字节的寄存器
reg [15:0]          r_generate_checksum ;   //根据读的数据,诸位累加
reg                 r_check_checksum    ;   //检查校验和,正确的时候拉高电平
reg                 r_recive_end_flag   ;   //接受完1帧数据的标志信号
reg [7:0]           r_send_byte_bit     ;   //当解帧正确的时候,将收到的10自己饿饿发出去
reg [3:0]           r_valid_cnt         ;   //用来延长数据的持续时间,缩短有效信号的时间
/***************wire******************/
/***************component*************/
/***************assign****************/
assign              o_data  = ro_data   ;
assign              o_valid = ro_valid  ;
assign              o_rd_en = ro_rd_en  ;   //控制解帧模块的读使能信号,每拉高电平,读出数据

/***************always****************/

always@(posedge i_clk,negedge i_rst_n)
    if(~i_rst_n)
        ri_data_1d <= 'd0;
    else
        ri_data_1d <= i_data;

always@(posedge i_clk,negedge i_rst_n)
    if(~i_rst_n)
        ro_rd_en <= 'd1;
    else if(r_byte_cnt == P_SEND_BYTE_LENGTH || i_empty)
        ro_rd_en <= 'd0;
    else if(r_send_byte_bit == P_SEND_BYTE_LENGTH - 6)                        //使能信号会在下一周期拉低,因此多持续了一个时钟周期,因此每次读出的是2字节的帧头
        ro_rd_en <= 'd1;
    else if(r_byte_cnt < P_SEND_BYTE_LENGTH && ~i_empty && r_send_byte_bit == 0)                        //FIFO中有数据的时候,拉高使能
        ro_rd_en <= 'd1;
    else
        ro_rd_en <= ro_rd_en;

always@(posedge i_clk,negedge i_rst_n)
    if(~i_rst_n)
        r_byte_cnt <= 'd0;
    else if(r_byte_cnt == P_SEND_BYTE_LENGTH)
        r_byte_cnt <= 'd0;
    else if(r_byte_cnt == 1 && ri_data_1d != P_FIRST_MARK)
        r_byte_cnt <= 'd0;
    else if(r_byte_cnt == 2 && ri_data_1d != P_SECOND_MARK)
        r_byte_cnt <= 'd0;
    else if(i_valid)
        r_byte_cnt <= r_byte_cnt + 1;
    else
        r_byte_cnt <= r_byte_cnt;

always@(posedge i_clk,negedge i_rst_n)      //检测是否符合帧头,符合枕头第一字节就加一,符合帧头第二字节就加二,只有连续符合两个帧头才开始解帧,即当计数到二时候才开始解帧
    if(~i_rst_n)
        r_is_mark_cnt <= 'd0;
    else if(r_send_byte_bit == P_SEND_BYTE_LENGTH - 6)
        r_is_mark_cnt <= 'd0;
    else if(r_byte_cnt == 0 &&i_valid)begin
        if(i_data == P_FIRST_MARK)
            r_is_mark_cnt <= r_is_mark_cnt + 1;
        else
            r_is_mark_cnt <= r_is_mark_cnt;
    end
    else if(r_byte_cnt == 1 && i_valid)begin
        if(i_data == P_SECOND_MARK)
            r_is_mark_cnt <= r_is_mark_cnt + 1;
        else
            r_is_mark_cnt <= r_is_mark_cnt;
    end
    else
        r_is_mark_cnt <= r_is_mark_cnt;


always @(posedge i_clk,negedge i_rst_n) begin
    if(~i_rst_n)
        r_is_mark_cnt_1d <= 'd0;
    else
        r_is_mark_cnt_1d <= r_is_mark_cnt;
end

always @(posedge i_clk,negedge i_rst_n) begin
    if(~i_rst_n)
        r_unframe_en <= 'd0;
    else if(r_is_mark_cnt == 2)
        r_unframe_en <= 'd1;
    else if(r_byte_cnt == P_SEND_BYTE_LENGTH)       //解帧结束的时候拉低
        r_unframe_en <= 'd0;
    else
        r_unframe_en <= r_unframe_en;
end


always @(posedge i_clk,negedge i_rst_n) begin       //本地计数接收到的帧,然后与每帧的帧计数进行匹配
    if(~i_rst_n)
        r_frame_counter <= 'd0;
    else if(r_is_mark_cnt == 2 && r_is_mark_cnt_1d == 1)
        r_frame_counter <= r_frame_counter+1;
    else
        r_frame_counter <= r_frame_counter;
end

always @(posedge i_clk,negedge i_rst_n) begin       //获取收到的帧的帧计数
    if(~i_rst_n)
        r_frame_container <= 'd0;
    else if(r_byte_cnt == P_SEND_BYTE_LENGTH)
        r_frame_container <= 'd0;
    else if(r_byte_cnt == 3 && r_unframe_en)
        r_frame_container[7:0] <= ri_data_1d;
    else if(r_byte_cnt == 4 && r_unframe_en)
        r_frame_container[15:8] <= ri_data_1d;
    else
        r_frame_container <= r_frame_container;
end


always @(posedge i_clk,negedge i_rst_n) begin   //与本地应该受到的帧计数进行比较,在收到第一字节数据的的同时,进行帧匹配
    if(~i_rst_n)
        r_frame_is_True <= 'd0;
    else if(r_send_byte_bit == P_SEND_BYTE_LENGTH -6)
        r_frame_is_True <= 'd0;
    else if(r_byte_cnt == 4 && i_valid)
        r_frame_is_True <= r_frame_counter == r_frame_container?1:0;
    else
        r_frame_is_True <= r_frame_is_True;
end

always @(posedge i_clk,negedge i_rst_n) begin
    if(~i_rst_n)
        ri_valid_1d <= 'd0;
    else
        ri_valid_1d <= i_valid;
end


always @(posedge i_clk,negedge i_rst_n) begin   //把10个字节存着,等验证成功,就把10字节写入FIFO
    if(~i_rst_n)
        r_frame_data <= 'd0;
    else if(r_send_byte_bit == P_SEND_BYTE_LENGTH -6)
        r_frame_data <= 'd0;
    else if(r_byte_cnt > 4 && r_byte_cnt <= 14 && r_frame_is_True && r_unframe_en&&ri_valid_1d)
        r_frame_data <= {ri_data_1d,r_frame_data[79:8]};
    else if(r_valid_cnt == 3)       //在等于2的时候,已经把低八位取出去了,所以要右移8位
        r_frame_data <= r_frame_data>>8;
    else
        r_frame_data <= r_frame_data;
end


always @(posedge i_clk,negedge i_rst_n) begin   //读数据,进行累加,生成累加校验
    if(~i_rst_n)
        r_generate_checksum <= 'd0;
    else if(r_byte_cnt == 4)                    //接受下一帧的时候才归零
        r_generate_checksum <= 'd0;
    else if(r_byte_cnt > 4 && r_byte_cnt <= 14 && r_frame_is_True && r_unframe_en&&ri_valid_1d)
        r_generate_checksum <= r_generate_checksum + ri_data_1d;
    else if(r_byte_cnt == P_SEND_BYTE_LENGTH)
        r_generate_checksum <= ~r_generate_checksum;
    else
        r_generate_checksum <= r_generate_checksum;
end

always @(posedge i_clk,negedge i_rst_n) begin       //获取收到的帧的checksum校验和
    if(~i_rst_n)
        r_checksum_container <= 'd0;
    else if(r_send_byte_bit == P_SEND_BYTE_LENGTH -6)
        r_checksum_container <= 'd0;
    else if(r_byte_cnt == P_SEND_BYTE_LENGTH - 1 && r_frame_is_True &&ri_valid_1d)
        r_checksum_container[7:0] <= ri_data_1d;
    else if(r_byte_cnt == P_SEND_BYTE_LENGTH && r_frame_is_True && ri_valid_1d)
        r_checksum_container[15:8] <= ri_data_1d;
    else
        r_checksum_container <= r_checksum_container;
end

always @(posedge i_clk,negedge i_rst_n) begin   //接收完就拉高一个周期,其他时候都是低电平
    if(~i_rst_n)
        r_recive_end_flag <= 'd0;
    else if(r_byte_cnt == P_SEND_BYTE_LENGTH) 
        r_recive_end_flag <= 'd1;
    else
        r_recive_end_flag <= 'd0;
end

always @(posedge i_clk,negedge i_rst_n) begin   //进行和校验,校验成功拉高电平,进行发送
    if(~i_rst_n)
        r_check_checksum <= 'd0;
    else if(r_send_byte_bit == P_SEND_BYTE_LENGTH - 6) 
        r_check_checksum <= 'd0;
    else if(r_recive_end_flag) 
        r_check_checksum <= r_checksum_container == r_generate_checksum?1:0;
    else
        r_check_checksum <= r_check_checksum;
end

always @(posedge i_clk,negedge i_rst_n) begin   //发送时候的字节计数器
    if(~i_rst_n)
        r_send_byte_bit <= 'd0;
    else if(r_send_byte_bit == P_SEND_BYTE_LENGTH - 6)
        r_send_byte_bit <= 'd0;
    else if(ro_valid)
        r_send_byte_bit <= r_send_byte_bit + 1;
    else
        r_send_byte_bit <= r_send_byte_bit;
end

always @(posedge i_clk,negedge i_rst_n) begin
    if(~i_rst_n)
        ro_data <= 'd0;
    else if(r_send_byte_bit == P_SEND_BYTE_LENGTH - 6)
        ro_data <= 'd0;
    else if(r_valid_cnt == 2)
        ro_data <= r_frame_data[7:0];
    else
        ro_data <= ro_data;
    
end
   
always @(posedge i_clk,negedge i_rst_n) begin   //数据有效值的间隔周期计数器
    if(~i_rst_n)
        r_valid_cnt <= 'd0;
    else if(r_valid_cnt == 3)
        r_valid_cnt <= 'd0;
    else if(r_check_checksum)
        r_valid_cnt <= r_valid_cnt+1;
    else
        r_valid_cnt <= 'd0;
end

always @(posedge i_clk,negedge i_rst_n) begin
    if(~i_rst_n)
        ro_valid <= 'd0;
    else if(r_valid_cnt == 3 && ~i_full && r_check_checksum) //当full拉高的时候,就说明已满,写使能不再有效
        ro_valid <= 'd1;
    else
        ro_valid <= 'd0;
end
endmodul

3.4 顶层模块Multi_Uart

主要功能:顶层主要是用来隔离外界,控制各个底层模块,只暴露rx和tx管脚

框图:

参数控制

  • P_SEND_BYTE_LENGTH:发送的字节数,默认16字节
  • P_FIRST_MARK:帧头第一个标志位,默认8'HEB
  • P_SECOND_MARK:帧头第二个标志位,默认8'H90
  • P_OVER_SAMPLE:过采样次数,默认16
  • P_SYS_CLOCK:输入时钟,即PLL后的输入时钟,默认50Mhz
  • P_UART_BAUDRATE:波特率,默认9600.
  • P_UART_STOP_WIDTH:停止位宽度,默认1位
  • P_UART_PARITY_WIDTH:校验位,1代表奇校验,2代表偶检验,0代表无校验,默认奇校验
  • P_CRC_POLY:CRC校验多项式,默认CRC-16模型

参考代码

verilog 复制代码
`timescale 1ns / 1ns

module Multi_Uart
#(
    parameter               P_SEND_BYTE_LENGTH    =     16          ,   //发送的字节数
    parameter               P_FIRST_MARK          =     8'HEB       ,   //帧头标志位
    parameter               P_SECOND_MARK         =     8'H90       ,   //帧头标志位
    parameter               P_OVER_SAMPLE         =     16          ,   //接收模块的过采样次数
    parameter               P_SYS_CLOCK           =     50_000_000  ,   //系统时钟可配置,默认50MHZ
    parameter               P_UART_BAUDRATE       =     9600        ,   //波特率可配置,默认9600
    parameter               P_UART_STOP_WIDTH     =     1           ,   //停止位宽度,默认1位
    parameter               P_UART_PARITY_WIDTH   =     1           ,   //校验位宽度,0为无校验,1为奇校验,2位偶校验
    parameter               P_CRC_POLY            =     16'H8005        //CRC校验中的多项式,执行的CRC-8/MAXIM标准
)
(
    input                   i_sys_clk_p                             ,
    input                   i_sys_clk_n                             ,
    input                   i_sys_rst_n                             ,
    input                   i_rx                                    ,
    output                  o_tx        
);

localparam                  P_BAUD_CNT = (P_SYS_CLOCK/P_UART_BAUDRATE)/P_OVER_SAMPLE;

wire                        w_clk_50MHZ         ;   //pll后输出的50Mhz时钟
wire                        w_pll_locked        ;   //锁相环的上锁信号
wire                        w_sys_rst_n         ;   //PLL lock信号产生的复位信号
wire                        wo_tx               ;
wire                        w_over_clk          ;   //波特率的P_OVER_SAMPLE倍的时钟

assign                      w_sys_rst_n = w_pll_locked;
assign                      o_tx = wo_tx;

wire                        w_pll_rst_p         ;
assign                      w_pll_rst_p = ~i_sys_rst_n;

System_Pll System_Pll_U1
   (
    .clk_in1_p              (i_sys_clk_p        )   ,   // input clk_in1_p
    .clk_in1_n              (i_sys_clk_n        )   ,   // input clk_in1_n
    .reset                  (w_pll_rst_p        )   ,   // input reset
    .sys_clk                (w_clk_50MHZ        )   ,   // output sys_clk
    .locked                 (w_pll_locked       )      // output locked
);


//分频模块
clk_div#(
    .P_CLK_DIV_CNT          (P_BAUD_CNT        )       //默认4分频,最大为65535
)
clk_div_u0
(
    .i_clk                  (w_clk_50MHZ        )   ,
    .i_rst_n                (w_sys_rst_n        )   ,

    .o_clk_div              (w_over_clk         )       //9.6K级别的频率
);

wire                        w_fifo_Unframe_full     ;   //判断即将写入的FIFO是否已满
wire    [7:0]               w_rx_o_data             ;
wire                        w_rx_o_valid            ;


//接收模块
Rx_Drive#(
    .P_SYS_CLOCK            (P_SYS_CLOCK        )    ,   //系统时钟可配置,默认50MHZ
    .P_UART_BAUDRATE        (P_UART_BAUDRATE    )    ,   //波特率可配置,默认9600
    .P_UART_STOP_WIDTH      (P_UART_STOP_WIDTH  )    ,   //停止位宽度,默认1位
    .P_UART_PARITY_WIDTH    (P_UART_PARITY_WIDTH)    ,   //校验位,默认0,0不校验,1奇校验,2偶校验
    .P_OVER_SAMPLE          (P_OVER_SAMPLE      )        //过采样的时钟的倍率
)
Rx_Drive_U0
(
    .i_clk                  (w_over_clk         )   ,
    .i_rst_n                (w_sys_rst_n        )   ,
    .i_full                 (w_fifo_Unframe_full)   ,   //判断待写入的FIFO是否已满
    .i_rx                   (i_rx               )   ,   //目前在拼回环用的是输出tx接入输入rx,后面应该就接rx输入

    .o_rx_data              (w_rx_o_data        )   ,
    .o_rx_valid             (w_rx_o_valid       )   
);

wire    [7:0]               w_fifo_Unframe_data     ;   //FIFO输出的数据
wire                        w_fifo_Unframe_valid    ;   //FIFO输出的数据有效信号
wire                        w_fifo_Unframe_empty    ;   //读之前判断FIFO是否为空,当为空的时候,高电平,读使能不再有效
wire                        w_fifo_rst_p            ;   //fifo复位

wire                        w_Unframe_rd_en         ;   //组帧的读使能信号
assign                      w_fifo_rst_p = ~w_sys_rst_n;


//异步FIFO,主要用在解帧前的数据缓存
FIFO_UnFrame_32_8 FIFO_UnFrame_32_8_U0 (
    .clk                    (w_over_clk         )   ,   // input wire clk
    .srst                   (w_fifo_rst_p       )   ,   // input wire srst
    .din                    (w_rx_o_data        )   ,   // input wire [7 : 0] din
    .wr_en                  (w_rx_o_valid       )   ,   // input wire wr_en
    .full                   (w_fifo_Unframe_full)   ,   // output wire full

    .rd_en                  (w_Unframe_rd_en    )   ,   // input wire rd_en
    .empty                  (w_fifo_Unframe_empty)  ,   // output wire empty
    .dout                   (w_fifo_Unframe_data)   ,   // output wire [7 : 0] dout
    .valid                  (w_fifo_Unframe_valid)      // output wire valid
);

wire        [7:0]           w_fifo_data_i_data      ;   //输入fifo-data的数据
wire                        w_fifo_data_i_valid     ;   //输入输入fifo-data的有效信号
wire                        w_fifo_data_o_full      ;   //判断fifo-data是否写满,写满了就拉高

//解帧模块
Unframe
#(
    .P_CRC_POLY             (P_CRC_POLY         )   ,   //CRC校验中的多项式
    .P_FIRST_MARK           (P_FIRST_MARK       )   ,
    .P_SECOND_MARK          (P_SECOND_MARK      )   ,
    .P_SEND_BYTE_LENGTH     (P_SEND_BYTE_LENGTH )    
)
Unframe_U0
(
    .i_clk                  (w_over_clk         )   ,
    .i_rst_n                (w_sys_rst_n        )   ,
    .i_full                 (w_fifo_data_o_full )   ,   //判断解帧后的FIFO是否满
    .i_empty                (w_fifo_Unframe_empty)   ,   //在读入的时候,判断FIFO是否为空
    .i_data                 (w_fifo_Unframe_data)   ,
    .i_valid                (w_fifo_Unframe_valid)   ,

    .o_data                 (w_fifo_data_i_data )   ,
    .o_valid                (w_fifo_data_i_valid)   ,
    .o_rd_en                (w_Unframe_rd_en    )   
);


wire        [7:0]           w_frame_o_data          ;   //组帧模块输出的数据
wire                        w_frame_o_valid         ;   //组帧模块输出的数据有效信号
wire                        w_frame_fifo_o_full     ;   //组帧后,判断即将写入的FIFO是否已满
wire                        w_fifo_data_rst_p       ;   //FIFo的复位位高电平复位
wire                        w_fifo_data_o_rd_en     ;   //组帧模块输出的读使能信号
wire                        w_fifo_frame_empty      ;   //组帧模块在读之前,判断fifo-data是否为空
wire        [7:0]           w_frame_i_data          ;   //输入组帧模块的数据
wire                        w_frame_i_valid         ;   //输入组帧模块的数据有效信号

assign                      w_fifo_data_rst_p = ~w_sys_rst_n;

//同步fifo:接收数据的fifo-data
FIFO_Data_32_8 FIFO_Data_32_8_U0 (
    .clk                    (w_over_clk         )   ,   // input wire clk
    .srst                   (w_fifo_data_rst_p  )   ,   // input wire srst
    .din                    (w_fifo_data_i_data )   ,   // input wire [7 : 0] din
    .wr_en                  (w_fifo_data_i_valid)   ,   // input wire wr_en

    .rd_en                  (w_fifo_data_o_rd_en)   ,   // input wire rd_en
    .dout                   (w_frame_i_data     )   ,   // output wire [7 : 0] dout
    .full                   (w_fifo_data_o_full )   ,   // output wire full
    .empty                  (w_fifo_frame_empty )   ,   // output wire empty
    .valid                  (w_frame_i_valid    )       // output wire valid
);


//组帧模块:将2byte标志位+ 2byte帧计数+10byte数据+2byte和校验位
Frame
#(
    .P_CRC_POLY             (P_CRC_POLY         )   ,   
    .P_FIRST_MARK           (P_FIRST_MARK       )   ,
    .P_SECOND_MARK          (P_SECOND_MARK      )   ,
    .P_SEND_BYTE_LENGTH     (P_SEND_BYTE_LENGTH )   
)Frame_U0
(
    .i_clk                  (w_over_clk         )   ,
    .i_rst_n                (w_sys_rst_n        )   ,
    .i_data                 (w_frame_i_data     )   ,
    .i_valid                (w_frame_i_valid    )   ,
    .i_full                 (w_frame_fifo_o_full)   ,   //写满信号,当为高电平时,写使能不再有效
    .i_empty                (w_fifo_frame_empty )   ,   //读空信号,当为高电平时,读使能不再有效

    .o_rd_en                (w_fifo_data_o_rd_en)   ,  //读使能信号,当fifo非空且需要读数据的拉高
    .o_data                 (w_frame_o_data     )   ,
    .o_valid                (w_frame_o_valid    )
);


wire        [7:0]           w_fifo_tx_i_data        ;   //发送模块输出的待发送八位数据    
wire                        w_fifo_tx_i_valid       ;
wire                        w_fifo_tx_o_empty       ;
wire                        w_fifo_tx_i_rd_en       ;

wire                        w_fifo_frame_rst_p      ;
assign                      w_fifo_frame_rst_p = !w_sys_rst_n;


//同步FIFO,主要用于缓存 组帧模块和字节发送模块的数据流
FIFO_Frame_32_8  FIFO_Frame_32_8_U0(
    .clk                    (w_over_clk         )   ,  // input wire clk
    .srst                   (w_fifo_frame_rst_p )   ,  // input wire srst
    .din                    (w_frame_o_data     )   ,  // input wire [7 : 0] din
    .wr_en                  (w_frame_o_valid    )   ,  // input wire wr_en
    .rd_en                  (w_fifo_tx_i_rd_en  )   ,  // input wire rd_en
    .dout                   (w_fifo_tx_i_data   )   ,  // output wire [7 : 0] dout
    .valid                  (w_fifo_tx_i_valid  )   ,  // output wire valid
    .full                   (w_frame_fifo_o_full)   ,  // output wire full
    .empty                  (w_fifo_tx_o_empty  )      // output wire empty
);


//单字节发送驱动模块
Tx_Drive
#(
    .P_SYS_CLOCK            (P_SYS_CLOCK        )   ,   //系统时钟可配置,默认50MHZ
    .P_UART_BAUDRATE        (P_UART_BAUDRATE    )   ,   //波特率可配置,默认9600
    .P_UART_STOP_WIDTH      (P_UART_STOP_WIDTH  )   ,   //停止位宽度,默认1位
    .P_UART_PARITY_WIDTH    (P_UART_PARITY_WIDTH)   ,   //校验位,默认0,0不校验,1奇校验,2偶校验
    .P_OVER_SAMPLE          (P_OVER_SAMPLE      )
)
Tx_Drive_U0
(
    .i_sys_clk              (w_over_clk         )   ,
    .i_rst_n                (w_sys_rst_n        )   ,
    .i_valid                (w_fifo_tx_i_valid  )   ,
    .i_data                 (w_fifo_tx_i_data   )   ,
    .i_empty                (w_fifo_tx_o_empty  )   ,
    .o_tx                   (wo_tx              )   ,                               
    .o_tx_ready             (w_fifo_tx_i_rd_en  )       //发送模块空闲信号,当为高电平时,代表空闲

);
endmodule

3.5 其他模块

3.5.1 分频模块CLK_DIV

主要功能:输出分频后的时钟

程序代码:

verilog 复制代码
`timescale 1ns / 1ps

module clk_div#(
    parameter   P_CLK_DIV_CNT = 4  //默认4分频,最大为65535
)(
    input   wire    i_clk       ,
    input   wire    i_rst_n     ,
    output  wire    o_clk_div
);

/***************reg*******************/
reg                 ro_clk_div          ;   //输出时钟
reg         [15:0]  r_cnt               ;   //分频计数器
/***************wire******************/
/***************component*************/
/***************assign****************/
assign  o_clk_div = ro_clk_div  ;
/***************always****************/

//计数器
always @(posedge i_clk, negedge i_rst_n) begin
    if(~i_rst_n)
        r_cnt   <=  'd0;
    else if(r_cnt == (P_CLK_DIV_CNT >> 1)-1)
        r_cnt   <=  'd0;
    else
        r_cnt   <=  r_cnt + 1;
end

//输出分频
always @(posedge i_clk, negedge i_rst_n) begin
    if(~i_rst_n)
        ro_clk_div <= 1'd0;
    else if(r_cnt == (P_CLK_DIV_CNT >> 1)-1)
        ro_clk_div <= ~ro_clk_div;
    else
         ro_clk_div <=  ro_clk_div;
end
endmodule
相关推荐
DS小龙哥2 小时前
基于Zynq FPGA的雷龙SD NAND存储芯片性能测试
fpga开发·sd nand·雷龙·spi nand·spi nand flash·工业级tf卡·嵌入式tf卡
爱吃生蚝的于勒10 小时前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
城南vision11 小时前
计算机网络——HTTP篇
网络协议·计算机网络·http
上理考研周导师11 小时前
第二章 虚拟仪器及其构成原理
fpga开发
FPGA技术实战13 小时前
《探索Zynq MPSoC》学习笔记(二)
fpga开发·mpsoc
幺零九零零17 小时前
【计算机网络】TCP协议面试常考(一)
服务器·tcp/ip·计算机网络
长安初雪21 小时前
Request 和 Response 万字详解
计算机网络·servlet
ZachOn1y1 天前
计算机网络:运输层 —— 运输层概述
网络·tcp/ip·计算机网络·运输层
黎鹤舞8281 天前
计算机网络易混淆知识点串记
计算机网络