16字节协议的串口通信

news2024/11/25 5:41:21

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发送一帧

框图

在这里插入图片描述

参考代码

`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

参考代码:

`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

参考代码:

`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字节。

框图:

在这里插入图片描述

参考代码

`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倍

框图:

在这里插入图片描述

参考代码

`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)就说要发下一个数据,直接进入起始状态。

参考代码:

`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倍采样。

参考代码

`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。

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

`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:发送字节

参考代码

`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模型

参考代码

`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

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

程序代码:

`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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/977575.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

esp32s3实现openmv

演示参考下方视频 源码链接在视频末尾获取点击查看视频 摄像头引脚配置 烧录配置

【Linux】进程基础概念【下篇】

目录 1. 基本概念 2. 常见环境变量 常见环境变量指令 &#xff08;1. PATH &#xff08;2. HOME &#xff08;3. SHELL 3.环境变量的组织形式 &#xff08;1&#xff09;通过代码如何获取环境变量 &#xff08;2&#xff09;普通变量与环境变量的区别 &#xff08;3&…

Vue+NodeJS实现邮件发送

一.邮箱配置 这里以QQ邮箱为例,网易邮箱类似. 设置->账号 二.后端服务搭建 index.js const express require(express) const router require(./router); const app express()// 使用路由文件 app.use(/,router);app.listen(3000, () > {console.log(server…

大学大创项目:手机室内AR导航APP项目思路

文章目录 一、最初的项目思路二、建图和定位分离的项目思路1、建图2、定位 个人见解&#xff0c;如有错误&#xff0c;请多包涵 一、最初的项目思路 在大创项目的开始&#xff0c;将手机确定为应用设备&#xff0c;传感器确定为相机。 由于知识储备的原因&#xff0c;在头一次…

Jenkins实现基础CI操作

操作截图 代码push进gitlab Jenkins拉取gitlab代码 在容器内Jenkins拉取的代码

app备案ios的公钥和md5的获取方法

最近app需要备案才能上架了 但是app备案的时候&#xff0c;特别是ios备案的时候需要提供app的公钥和md5比较头大&#xff0c;无论是android系统还是ios系统&#xff0c;都需要提供证书的公钥和md5。 获取这个公钥和md5真的好麻烦&#xff0c;好像各种工具都没有提供获取这些信…

java数据结构1------深入学习ArrayList

目录 一、概念 二、源码分析 1、属性 2、构造器 ①空构造 ②指定初始容量&#xff08;initialCapacity&#xff09;构造器 ②参数为Collection的构造器 3、常用方法 ①public boolean add(E e) ②public void add(int index, E element) ③其他方法 三、总结 一、概念…

【ARM AMBA5 CHI 入门 12 -- CHI 总线学习 】

文章目录 介绍CHI 特点Layers of the CHI architectureTopology Node TypeTransaction 分类Transaction 路由SAM 介绍Node ID 节点间数据怎么传输的呢&#xff1f; 介绍 CHI 的全称是 Coherent Hub Interface。所以从名字就能看出&#xff0c;CHI要解决什么问题了。按照惯例&a…

思维导图怎么变成ppt?4个思维导图一键生成ppt的方法

做好的思维导图如何变成一份ppt&#xff1f;本文罗列了4个可行方法&#xff0c;一起来看看吧。 一 直接复制粘贴 这是最简单的方法&#xff0c;虽然这样可能会花费一些时间&#xff0c;但可以确保内容排版和布局与你想要的一致。当然&#xff0c;我们大可使用更高效的方法。…

NLP(2)--Transformer

目录 一、Transformer概述 二、输入和输出 三、Encoder 四、Decoder 五、正则化处理 六、对于结构的改进&#xff1f; 七、AT vs NAT 八、Cross-attention 一、Transformer概述 Transformer模型发表于2017年Google团队的Attention is All you need这篇论文&#xff0c;…

七、SSM 框架整合

目前已经学习了 MyBatis 框架&#xff0c;Spring 框架&#xff0c;以及Spring MVC 框架。现阶段学习将这三个框架整合到一起&#xff0c;实现简单的前后端交互的曾删改差功能页面。 Mybatis 框架主要负责数据库的操作问题&#xff0c;以及数据回显。该框架将 SQL 与 Jav…

Browserslist 信息和配置使用整理

我们可以在各种前端工程看到 Browserslist 的配置身影&#xff0c;看似简单但实际上可能会有暗坑导致线上兼容问题&#xff0c;借此文来整理下 Browserslist 的信息。 Browserslist 是由 Autoprefixer 团队维护的一个开源项目&#xff0c;用于自动处理 CSS 和 JavaScript 文件…

opencv识别一张图片的多个红框,并截取红框的内容

需求 需要获取图片的红框的内容&#xff0c;实体的图片我就不放了 获取红框 先截取获得图片的多个轮廓 import cv2 import numpy as np # 加载图像并转换为灰度图像 image cv2.imread(image6.jpg) gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 应用高斯模糊以减…

基本的 Linux 命令以及 Linux 目录结构

目录 什么是Linux&#xff1f; ls - 列出文件和目录 pwd - 显示当前工作目录 cd - 切换目录 mkdir - 创建目录 touch - 创建空文件 rm - 删除文件和目录 cp - 复制文件和目录 mv - 移动和重命名文件和目录 文件系统基础 Linux 操作系统是开源且强大的操作系统&…

流媒体之推流和拉流

推流&#xff1a;将直播内容推送至服务器的过程 拉流&#xff1a;为服务器已有直播内容&#xff0c;用指定地址进行拉取的过程 什么是推流&#xff1f; 推流&#xff0c;指的是把采集阶段封包好的内容传输到服务器的过程。其实就是将现场的视频信号传到网络的过程。“推流”…

D. Sorting By Multiplication(贪心)

Problem - D - Codeforces 给定一个长度为n的数组a&#xff0c;由正整数组成。 您可以对该数组执行以下操作任意次数&#xff08;可能为零&#xff09;&#xff1a; 选择三个整数l、r和x&#xff0c;使得1≤l≤r≤n&#xff0c;并将满足l≤i≤r的每个ai乘以x。 请注意&#…

操作系统内存(32位为例)

0、OS能使用最大的虚拟内存和物理内存 最大的虚拟内存与寻址总线有关。一般是40根&#xff0c;对应256T 最大的物理内存与PTE的位数有关。 10-10-12分页模式下是32位&#xff0c;所以最大寻址空间就4G 1、CPU分页模式 分类 还有5-level&#xff0c;一般适用于大型服务器。…

【Springcloud】Seata分布式事务

【Springcloud】Seata分布式事务 【一】基本介绍&#xff08;1&#xff09;什么是分布式事务&#xff08;2&#xff09;为什么要使用分布式事务&#xff08;3&#xff09;seata分布式事务 【二】下载方式【1】Windows平台安装包下载 【三】如何使用【1】创建相关测试数据库和表…

每日刷题-1

目录 一、选择题 二、编程题 1、组队竞赛 2、删除公共字符 一、选择题 1、 解析&#xff1a;%s遇到\0会停止&#xff0c;%m.ns中m表示字符串宽度&#xff0c;n表示左起取目标字符串n个字符&#xff0c;右对齐&#xff0c;&#xff08;如果想要左对齐&#xff0c;可以写成%-m.n…

【C++学习笔记】5、变量作用域

文章目录 【 1、局部变量 】【 2、全局变量 】【 3、局部变量和全局变量的初始化 】 作用域是程序的一个区域&#xff0c;一般来说有三个地方可以定义变量&#xff1a; 在函数或一个代码块内部声明的变量&#xff0c;称为局部变量。 在函数参数的定义中声明的变量&#xff0c;称…