基于GTX 8B10B编码的自定义PHY接收模块(高速收发器十三)

  点击进入高速收发器系列文章导航界面


  前文完成了发送模块的设计,本文接着完成接收模块的设计,接收模块相对发送模块会更加麻烦。

1、设计思路

  前文在讲解官方示例工程时,提到GTX IP的接收部分没有做字对齐,需要用户自己编写字对齐模块,由于发送端可能发送任意字节数据,因此停止位也可能出现在一帧数据的任意位置。

  下图是自定义的帧格式,在实际收发数据过程中,部分逗号可能被用于时钟纠正。在检测数据帧时,将24’hbc50fb作为一帧数据的帧头,帧尾依旧是8’hfd。

在这里插入图片描述

图1 帧格式

  由于GTX IP的接收通道没有进行字对齐,接收的帧头和帧尾的数据可能会出现下图中的16种现象。

在这里插入图片描述

图2 帧头帧尾的分布

  起始位8’hfb可能出现在一个数据的任何字节位置,停止位8’hfd也可能出现在一个数据的任意字节位置(因为发送端可能发送任意字节数据)。这些条件会使得输出给用户的最后一个数据的部分数据无效,需要用掩码进行控制。

  蓝色数字用于记录起始位和停止位在所处数据中哪个字节,这与最后一个数据的掩码信号直接相关。

  黄色的掩码表示这个数据会出现在下一个数据中,与停止位对齐的数据都i是有效的,由三种情况出现这种情况。

  可以对图2中的规律进行总结,首先当停止位所在字节数减去起始位所在字节数等于1时,输出给用户的最后一个数据所有字节均有效。

  当起始位所在位置与停止位所在位置相差2时,输出给用户的最后一个数据只有最高字节有效。

  当停止位所在字节数减去起始位所在字节数等于3 或者 起始位所在字节数减去停止位所在字节数等于1时,输出给用户的最后一个数据高两字节数据均有效。

  当起始位和停止位所在的位置相等时,输出给用户最后一个数据的高三字节均有效。

  本次主要思路是使用移位寄存器对GTX IP输出数据进行暂存,然后检测帧头,并且确定起始位和停止位在数据中的位置,便于后续生成数据掩码信号。

  当检测到帧头之后,就可以根据起始位所在位置向用户输出数据了,由于模块内部没有存储结构,因此数据信号不需要等待用户的应答信号。

  本模块设计的难点在于一帧数据的最后一个数据的掩码信号和最后一个数据有效指示信号的生成,其余信号都比较简单。

2、代码讲解

  本模块并没有使用计数器、状态机、FIFO等结构,仅仅以输出数据和K码指示信号的移位寄存器作为主体,完成设计。

  首先是端口部分,包括GTX IP输出端口的输出数据、K码指示信号、字节对齐指示信号,用户端口是去掉数据应答信号的axi_stream流端口,如下所示。

module phy_rx (
    input									clk		        ,//系统时钟信号;
    input									rst 	        ,//系统复位信号,高电平有效;

    output  reg         [31 : 0]            axi_s_data      ,//输出接收的数据,先输出高字节数据;
    output  reg         [3 : 0]             axi_s_keep      ,//输出接收数据有效指示信号;
    output  reg                             axi_s_last      ,//输出一帧数据的最后一个数据指示信号;
    output  reg                             axi_s_valid     ,//输出数据有效指示信号;

    input                                   gt_rx_bytealign ,//输入数据字节对齐指示信号;
    input               [31 : 0]            gt_rx_data      ,//输入待解析的数据;
    input               [3 : 0]             gt_rx_char       //输入数据K码指示信号,高电平有效;
);

  下面是移位寄存器,对GTX IP输出的数据和K码指示信号暂存,注意在写移位寄存器的时候,尽量不要写复位相关的代码,因为移位寄存器不需要复位。

    //将接收的数据和K码指示信号暂存,用于后续处理。
    always@(posedge clk)begin
        {gt_rx_char_r[2],gt_rx_char_r[1],gt_rx_char_r[0]} <= {gt_rx_char_r[1],gt_rx_char_r[0],gt_rx_char};
        {gt_rx_data_r[2],gt_rx_data_r[1],gt_rx_data_r[0]} <= {gt_rx_data_r[1],gt_rx_data_r[0],gt_rx_data};
        eof_flag_r <= {eof_flag_r[0],eof_flag};
    end

  下面检测图2中可能出现进行四种帧头的数据格式,检测到帧头后把commoa_flag拉高一个时钟,与gt_rx_data_r[1]对齐。

    //当检测到24'hBC50FB时,表示检测到同步码且检测到起始位,同步标志信号与gt_rx_data_r[1]对齐。
    //为了后续方便生成keep信号,只能将数据打两拍在进行检测。
    always@(posedge clk)begin
        if(rst)begin//初始值为0;
            commoa_flag <= 1'b0;
        end//检测到的序列是32'h50bc50bc,32'hXXXXXXfb时拉高,表示检测到同步码。
        else if((gt_rx_data_r[1][31:16] == 16'h50bc && gt_rx_char_r[1][3:2] == 2'b01) && (gt_rx_data_r[0][7:0] == 8'hfb && gt_rx_char_r[0][0] == 1'b1))begin
            commoa_flag <= 1'b1;
        end//检测到的序列是32'hBC50BCXX,32'hXXXXfb50时拉高,表示检测到同步码。
        else if((gt_rx_data_r[1][31:24] == 8'hbc && gt_rx_char_r[1][3] == 1'b1) && (gt_rx_data_r[0][15:0] == 16'hfb50 && gt_rx_char_r[0][1:0] == 2'b10))begin
            commoa_flag <= 1'b1;
        end//检测到的序列是32'h50BCXXXX,32'hXXfb50BC时拉高,表示检测到同步码。
        else if(gt_rx_data_r[0][23:0] == 24'hfb50bc && gt_rx_char_r[0][2:0] == 3'b101)begin
            commoa_flag <= 1'b1;
        end//检测到的序列是32'hBCXXXXXX,32'hfb50BC50时拉高,表示检测到同步码。
        else if(gt_rx_data_r[0][31:8] == 24'hfb50bc && gt_rx_char_r[0][3:1] == 3'b101)begin
            commoa_flag <= 1'b1;
        end
        else begin
            commoa_flag <= 1'b0;
        end
    end

  检测到帧头后,将接收数据标志信号(rx_data_flag)拉高,直到接收完一帧数据后拉低。

    //接收数据标志信号,与gt_rx_data_r[2]对齐。
    always@(posedge clk)begin
        if(rst)begin//初始值为0;
            rx_data_flag <= 1'b0;
        end
        else if(axi_s_last)begin//当检测到停止位时拉低。
            rx_data_flag <= 1'b0;
        end
        else if(commoa_flag && gt_rx_bytealign)begin//当检测到同步码和起始位后拉高,表示一帧数据的开始。
            rx_data_flag <= 1'b1;
        end
    end

  并且在检测到帧头后,需要记录起始位位于数据中的字节数。

    //检测到起始位标志信号,以及标志信号位于所在数据的字节数,与gt_rx_data_r[2]对齐。
    always@(posedge clk)begin
        if(rst)begin//初始值为0;
            sof_local <= 3'd0;
        end
        else if(commoa_flag && gt_rx_bytealign)begin//检测到同步码和起始位时,记录起始位位于该数据的字节位置。
            if((gt_rx_data_r[1][7:0] == 8'hfb) && gt_rx_char_r[1][0])
                sof_local <= 3'd0;
            else if((gt_rx_data_r[1][15:8] == 8'hfb) && gt_rx_char_r[1][1])
                sof_local <= 3'd1;
            else if((gt_rx_data_r[1][23:16] == 8'hfb) && gt_rx_char_r[1][2])
                sof_local <= 3'd2;
            else if((gt_rx_data_r[1][31:24] == 8'hfb) && gt_rx_char_r[1][3])
                sof_local <= 3'd3;
        end
    end

  之后需要检测一帧数据的帧尾8’hfd,并且记录停止位8’hfd在该数据中的字节数。

    //检测到停止位标志信号,以及记录停止位位于该数据的字节位置。
    always@(posedge clk)begin
        if(rst)begin//初始值为0;
            eof_flag <= 1'b0;
            eof_local <= 3'd0;
        end
        else if(rx_data_flag)begin//当接收数据时,检测停止位的位置。
            if((gt_rx_data[7:0] == 8'hfd) && gt_rx_char[0])begin//检测到停止位位于最低字节。
                eof_flag <= 1'b1;
                eof_local <= 3'd0;
            end
            else if((gt_rx_data[15:8] == 8'hfd) && gt_rx_char[1])begin
                eof_flag <= 1'b1;
                eof_local <= 3'd1;
            end
            else if((gt_rx_data[23:16] == 8'hfd) && gt_rx_char[2])begin
                eof_flag <= 1'b1;
                eof_local <= 3'd2;
            end
            else if((gt_rx_data[31:24] == 8'hfd) && gt_rx_char[3])begin//检测到停止位位于最高字节。
                eof_flag <= 1'b1;
                eof_local <= 3'd3;
            end
            else begin
                eof_flag <= 1'b0;
            end
        end
        else begin
            eof_flag <= 1'b0;
        end
    end

  当处于接收数据状态(rx_data_flag为高电平)时,根据起始位所在字节数,把接收的前后两字节数据拼接成输出数据。

  注意GTX IP输出数据是小端对齐,而输出给用户的数据是大端对齐,此处在输出数据时需要进行调换。比如起始位位于最高字节时,直接将gt_rx_data_r[1]的高低字节顺序调换输出即可。

    //生成输出数据,由于输出数据是先输出高字节数据,而接收的数据是先发送低字节数据,所以在拼接时需要进行纠正处理。
    always@(posedge clk)begin
        if(rst)begin//初始值为0;
            axi_s_data <= 32'd0;
        end
        else if(rx_data_flag)begin
            case (sof_local)//rx_data_flag与gt_rx_data_r[2]对齐,因此赋值时直接使用gt_rx_data_r[2]的信号。
                2'd0 : axi_s_data <= {gt_rx_data_r[2][15:8],gt_rx_data_r[2][23:16],gt_rx_data_r[2][31:24],gt_rx_data_r[1][7:0]};//将先接收的数据放在高字节处进行输出。
                2'd1 : axi_s_data <= {gt_rx_data_r[2][23:16],gt_rx_data_r[2][31:24],gt_rx_data_r[1][7:0],gt_rx_data_r[1][15:8]};//将先接收的数据放在高字节处进行输出。
                2'd2 : axi_s_data <= {gt_rx_data_r[2][31:24],gt_rx_data_r[1][7:0],gt_rx_data_r[1][15:8],gt_rx_data_r[1][23:16]};//将先接收的数据放在高字节处进行输出。
                2'd3 : axi_s_data <= {gt_rx_data_r[1][7:0],gt_rx_data_r[1][15:8],gt_rx_data_r[1][23:16],gt_rx_data_r[1][31:24]};//将先接收的数据放在高字节处进行输出。
                default : ;
            endcase
        end
    end

  至于数据掩码信号,通过两个always模块生成,第一个模块通过图2描述的方式生成最后一个数据的掩码信号。

    //计算最后一个输出数据的掩码信号;
    always@(posedge clk)begin
        if(rst)begin//初始值为0;
            axi_s_keep_r <= 4'hf;
        end
        else if(eof_flag)begin//结束标志有效时,通过起始位和停止位的位置,计算最后一个数据的有效字节位置。
            if(eof_local - sof_local == 3'd1)begin
                axi_s_keep_r <= 4'b1111;
            end
            else if((eof_local - sof_local == 3'd2) || (sof_local - eof_local == 3'd2))begin
                axi_s_keep_r <= 4'b1000;
            end
            else if((eof_local - sof_local == 3'd3) || (sof_local - eof_local == 3'd1))begin
                axi_s_keep_r <= 4'b1100;
            end
            else if(eof_local == sof_local)begin
                axi_s_keep_r <= 4'b1110;
            end
        end
    end

  但是部分掩码信号应该作用在后一个输出数据(图2中黄色部分掩码)。因此需要第二个always确定何时输出最后一个数据的掩码。

    //生成数据掩码信号;
    always@(posedge clk)begin
        if(rst)begin//初始值为4'hf;
            axi_s_keep <= 4'hf;
        end//当最后一个数据刚好在停止位所在数据时,将计算结果赋值输出。
        else if(((sof_local > 1) || ((sof_local <= 1) && (((eof_local - sof_local < 2) && (eof_local >= sof_local)) || (sof_local > eof_local)))) && eof_flag_r[0])begin
            axi_s_keep <= axi_s_keep_r;
        end
        else if((((sof_local <= 1) &&  (eof_local > sof_local) && (eof_local - sof_local >= 2))) && eof_flag_r[1])begin
            axi_s_keep <= axi_s_keep_r;
        end
        else begin//其余时间输出的数据均有效。
            axi_s_keep <= 4'hf;
        end
    end

  之后是一帧数据的结尾数据的指示信号,这个信号与K码指示信号类似,也是根据图二生成的信号,对应代码如下所示。

    //生成输出最后一个数据的指示信号;
    always@(posedge clk)begin
        if(rst)begin//初始值为0;
            axi_s_last <= 1'b0;
        end
        else if(sof_local > 1)begin
            if((sof_local == 3) && (eof_local == 0))//
                axi_s_last <= eof_flag;
            else
                axi_s_last <= eof_flag_r[0];
        end
        else if(((eof_local - sof_local < 2) && (eof_local >= sof_local)) || (sof_local > eof_local))begin
            axi_s_last <= eof_flag_r[0];
        end
        else if((eof_local > sof_local) && (eof_local - sof_local >= 2))begin
            axi_s_last <= eof_flag_r[1];
        end
        else begin
            axi_s_last <= 1'b0;
        end
    end

  最后是输出数据有效指示信号,接收数据后一直为高电平,直到向用户输出最后一个数据后拉低。

    //生成数据有效指示信号;
    always@(posedge clk)begin
        if(rst)begin//初始值为0;
            axi_s_valid <= 1'b0;
        end
        else if(axi_s_last)begin//接收完数据时拉低。
            axi_s_valid <= 1'b0;
        end
        else if(rx_data_flag)begin//开始接收数据时拉高。
            axi_s_valid <= 1'b1;
        end
    end

  当理解了图2后,整个设计其实难度也比较小,主要的就是掩码信号和最后数据指示信号的生成,以及一些信号之间的对齐关系。

3、模块仿真

  将PHY发送和接收模块例化形成PHY顶层模块,对应代码如下所示。

//--###############################################################################################
//--#
//--# File Name		: pdy_module
//--# Designer		: 数字站
//--# Tool			: Vivado 2021.1
//--# Design Date	: 2024.3.17
//--# Description	: 例化PHY的收发模块。
//--# Version		: 0.0
//--# Coding scheme	: GBK(If the Chinese comment of the file is garbled, please do not save it and check whether the file is opened in GBK encoding mode)
//--#
//--###############################################################################################
module phy_module (
    input               i_tx_clk        ,//发送部分时钟信号;
    input               i_tx_rst        ,//发送部分复位信号,高电平有效;
    input               i_rx_clk        ,//接收部分时钟信号;
    input               i_rx_rst        ,//接收模块复位信号,高电平有效;

    input  [31:0]       i_axi_s_data    ,//AXI的数据输入信号;
    input  [3 :0]       i_axi_s_keep    ,//AXI的数据掩码信号,低电平有效;
    input               i_axi_s_last    ,//AXI输入最后一个数据指示信号;
    input               i_axi_s_valid   ,//AXI输入数据有效指示信号;
    output              o_axi_s_ready   ,//AXI输入数据应答信号;

    output [31:0]       o_axi_s_data    ,//输出接收的数据,先输出高字节数据;
    output [3 :0]       o_axi_s_keep    ,//输出接收数据有效指示信号;
    output              o_axi_s_last    ,//输出一帧数据的最后一个数据指示信号;
    output              o_axi_s_valid   ,//输出数据有效指示信号;

    input               gt_tx_done      ,//GTX发送部分初始化成功,高电平有效;
    output [31:0]       gt_tx_data      ,//GTX发送数据;
    output [3 :0]       gt_tx_char      ,//GTX发送数据K码指示信号,高电平有效;
    input               gt_rx_bytealign ,//输入数据字节对齐指示信号;
    input  [31:0]       gt_rx_data      ,//输入待解析的数据;
    input  [3 :0]       gt_rx_char       //输入数据K码指示信号,高电平有效;
);
    //例化PHY芯片的发送模块。
    phy_tx u_phy_tx(
        .clk		    ( i_tx_clk      ),//发送部分时钟信号;
        .rst 	        ( i_tx_rst      ),//发送部分复位信号,低电平有效;
        .axi_s_data     ( i_axi_s_data  ),//AXI的数据输入信号;
        .axi_s_keep     ( i_axi_s_keep  ),//AXI的数据掩码信号,低电平有效;
        .axi_s_last     ( i_axi_s_last  ),//AXI输入最后一个数据指示信号;
        .axi_s_valid    ( i_axi_s_valid ),//AXI输入数据有效指示信号;
        .axi_s_ready    ( o_axi_s_ready ),//AXI输入数据应答信号;
        .gt_tx_done     ( gt_tx_done    ),//GTX发送部分初始化成功,高电平有效;
        .gt_tx_data     ( gt_tx_data    ),//GTX发送数据;
        .gt_tx_char     ( gt_tx_char    ) //GTX发送数据K码指示信号,高电平有效;
    );
    
    //例化PHY芯片的接收模块。
    phy_rx u_phy_rx(
        .clk		    ( i_rx_clk          ),//接收部分时钟信号;
        .rst 	        ( i_rx_rst          ),//接收模块复位信号,低电平有效;
        .axi_s_data     ( o_axi_s_data      ),//输出接收的数据,先输出高字节数据;
        .axi_s_keep     ( o_axi_s_keep      ),//输出接收数据有效指示信号;
        .axi_s_last     ( o_axi_s_last      ),//输出一帧数据的最后一个数据指示信号;
        .axi_s_valid    ( o_axi_s_valid     ),//输出数据有效指示信号;
        .gt_rx_bytealign( gt_rx_bytealign   ),//输入数据字节对齐指示信号;
        .gt_rx_data     ( gt_rx_data        ),//输入待解析的数据;
        .gt_rx_char     ( gt_rx_char        ) //输入数据K码指示信号,高电平有效;
    );
    
endmodule

  PHY顶层模块的TestBench如下所示,可以通过修改TX_KEEP和SOF_LOCAL来确定接收模块接收数据的起始位和停止位的位置,进而验证图2中16种数据格式。

//--###############################################################################################
//--#
//--# File Name		: tb_phy_module
//--# Designer		: 数字站
//--# Tool			: Vivado 2021.1
//--# Design Date	: 2024.3.17
//--# Description	: TestBench
//--# Version		: 0.0
//--# Coding scheme	: GBK(If the Chinese comment of the file is garbled, please do not save it and check whether the file is opened in GBK encoding mode)
//--#
//--###############################################################################################
`timescale 1 ns/1 ns
module tb_phy_module();
    localparam	CYCLE		= 	10		        ;//系统时钟周期,单位ns,默认10ns;
    localparam	RST_TIME	= 	10		        ;//系统复位持续时间,默认10个系统时钟周期;
    localparam  TX_KEEP     =   4'b1111         ;//发送最后一个数据的有效位数,大端对齐;
    localparam  SOF_LOCAL   =   0               ;//起始位位于第几字节。
    
    reg			                clk             ;//系统时钟,默认100MHz;
    reg			                rst_n           ;//系统复位,默认低电平有效;
    reg         [7 : 0]         send_value      ;

    reg         [31 : 0]        i_axi_s_data    ;
    reg         [3 : 0]         i_axi_s_keep    ;
    reg                         i_axi_s_last    ;
    reg                         i_axi_s_valid   ;
    wire                        o_axi_s_ready   ;
    wire        [31 : 0]        gt_tx_data      ;
    wire        [3  : 0]        gt_tx_char      ;

    reg [3 : 0]  gt_tx_char_r;
    reg [31 : 0] gt_tx_data_r;//

    wire [31: 0]    gt_tx_data_o    ;
    wire [3 : 0]    gt_tx_char_o    ;

    phy_module u_phy_module(
        .i_tx_clk       ( clk           ),//发送部分时钟信号;
        .i_tx_rst_n     ( rst_n         ),//发送部分复位信号,低电平有效;
        .i_rx_clk       ( clk           ),//接收部分时钟信号;
        .i_rx_rst_n     ( rst_n         ),//发送模块复位信号,低电平有效;
        .i_axi_s_data   ( i_axi_s_data  ),//AXI的数据输入信号;
        .i_axi_s_keep   ( i_axi_s_keep  ),//AXI的数据掩码信号,低电平有效;
        .i_axi_s_last   ( i_axi_s_last  ),//AXI输入最后一个数据指示信号;
        .i_axi_s_valid  ( i_axi_s_valid ),//AXI输入数据有效指示信号;
        .o_axi_s_ready  ( o_axi_s_ready ),//AXI输入数据应答信号;
        .o_axi_s_data   (               ),//输出接收的数据,先输出高字节数据;
        .o_axi_s_keep   (               ),//输出接收数据有效指示信号;
        .o_axi_s_last   (               ),//输出一帧数据的最后一个数据指示信号;
        .o_axi_s_valid  (               ),//输出数据有效指示信号;
        .gt_tx_done     ( 1'b1          ),//GTX发送部分初始化成功,高电平有效;
        .gt_tx_data     ( gt_tx_data    ),//GTX发送数据;
        .gt_tx_char     ( gt_tx_char    ),//GTX发送数据K码指示信号,高电平有效;
        .gt_rx_bytealign( 1'b1          ),//输入数据字节对齐指示信号;
        .gt_rx_data     ( gt_tx_data_o  ),//输入待解析的数据;
        .gt_rx_char     ( gt_tx_char_o  ) //输入数据K码指示信号,高电平有效;
    );

    always@(posedge clk)begin
        gt_tx_data_r <= gt_tx_data;
        gt_tx_char_r <= gt_tx_char;
    end

    //根据起始位的位置插入不同的数据。
    generate
        if(SOF_LOCAL == 0)begin
            assign gt_tx_data_o = gt_tx_data_r;
            assign gt_tx_char_o = gt_tx_char_r;
        end
        else if(SOF_LOCAL == 1)begin
            assign gt_tx_data_o = {gt_tx_data[23:0],gt_tx_data_r[31:24]};
            assign gt_tx_char_o = {gt_tx_char[2:0],gt_tx_char_r[3]};
        end
        else if(SOF_LOCAL == 2)begin
            assign gt_tx_data_o = {gt_tx_data[15:0],gt_tx_data_r[31:16]};
            assign gt_tx_char_o = {gt_tx_char[1:0],gt_tx_char_r[3:2]};
        end
        else if(SOF_LOCAL == 3)begin
            assign gt_tx_data_o = {gt_tx_data[7:0],gt_tx_data_r[31:8]};
            assign gt_tx_char_o = {gt_tx_char[0],gt_tx_char_r[3:1]};
        end
    endgenerate

    //生成周期为CYCLE数值的系统时钟;
    initial begin
        clk = 0;
        forever #(CYCLE/2) clk = ~clk;
    end

    //生成复位信号;
    initial begin
        rst_n = 1;
        #2;
        rst_n = 0;//开始时复位10个时钟;
        #(RST_TIME*CYCLE);
        rst_n = 1;
    end

    //生成输入信号din;
    initial begin
        i_axi_s_data  = 32'd0;
        i_axi_s_keep  = 4'd0;
        i_axi_s_last  = 1'd0;
        i_axi_s_valid = 1'd0;
        wait(rst_n);//等待复位完成;
        repeat(10) @(posedge clk);
        forever begin
            phy_tx_task(21);
        end
    end

    //发送数据的任务;
    task phy_tx_task(
        input	[7 : 0]		len
    );
        begin : phy_tx_task_0
            integer i;
            i_axi_s_data  <= 32'd0;
            i_axi_s_keep  <= 4'd0;
            i_axi_s_last  <= 1'd0;
            i_axi_s_valid <= 1'd0;
            send_value <= 8'd1;
            @(posedge clk);
            wait(o_axi_s_ready);
            for(i=0 ; i<len ; i=i+1)begin
                send_value <= send_value + 1;
                i_axi_s_data <= {send_value,send_value,send_value,send_value};
                if(i == len - 1)begin
                    i_axi_s_last <= 1'b1;
                    i_axi_s_keep <= TX_KEEP;
                end
                else begin
                    i_axi_s_last <= 1'b0;
                    i_axi_s_keep <= 4'hf;
                end
                i_axi_s_valid <= 1'b1;
                @(posedge clk);
            end
            i_axi_s_data  <= 32'd0;
            i_axi_s_keep  <= 4'd0;
            i_axi_s_last  <= 1'd0;
            i_axi_s_valid <= 1'd0;
            @(posedge clk);
        end
    endtask

endmodule

  如下图示,起始位和停止位均处于最低字节,输出给用户的最后一个数据里高三字节数据均有效。

在这里插入图片描述

图3 起始位和停止位均处于最低字节

  如下图示,起始位位于最低字节,停止位位于第1字节,那么输出给用户的最后一个数据的所有位均有效,仿真结果正确。

在这里插入图片描述

图4 起始位位于第0字节,停止位位于第1字节

  如下图示,起始位位于最低字节,停止位位于第2字节,那么输出给用户的最后一个数据的只有最高字节有效,仿真结果正确。

在这里插入图片描述

图5 起始位位于第0字节,停止位位于第2字节

  如下图示,起始位位于最低字节,停止位位于第3字节,那么输出给用户的最后一个数据的高两字节均有效,仿真结果正确。

在这里插入图片描述

图6 起始位位于第0字节,停止位位于第3字节

  如下图示,起始位位于第1字节,停止位位于第0字节,那么输出给用户的最后一个数据的高两字节均有效,仿真结果正确。

在这里插入图片描述

图7 起始位位于第1字节,停止位位于第0字节

  如下图示,起始位位于第1字节,停止位位于第1字节,那么输出给用户的最后一个数据的高三字节均有效,仿真结果正确。

在这里插入图片描述

图8 起始位位于第1字节,停止位位于第1字节

  如下图示,起始位位于第1字节,停止位位于第2字节,那么输出给用户的最后一个数据的所有字节均有效,仿真结果正确。

在这里插入图片描述

图9 起始位位于第1字节,停止位位于第2字节

  如下图示,起始位位于第1字节,停止位位于第3字节,那么输出给用户的最后一个数据只有最高字节有效,仿真结果正确。

在这里插入图片描述

图10 起始位位于第1字节,停止位位于第3字节

  如下图示,起始位位于第2字节,停止位位于第0字节,那么输出给用户的最后一个数据只有最高字节有效,仿真结果正确。

在这里插入图片描述

图11 起始位位于第2字节,停止位位于第0字节

  如下图示,起始位位于第2字节,停止位位于第1字节,那么输出给用户的最后一个数据只有最高两字节有效,仿真结果正确。

在这里插入图片描述

图12 起始位位于第2字节,停止位位于第1字节

  如下图示,起始位位于第2字节,停止位位于第2字节,那么输出给用户的最后一个数据只有高三字节均有效,仿真结果正确。

在这里插入图片描述

图13 起始位位于第2字节,停止位位于第2字节

  如下图示,起始位位于第2字节,停止位位于第3字节,那么输出给用户的最后一个数据所有字节均有效,仿真结果正确。

在这里插入图片描述

图14 起始位位于第2字节,停止位位于第3字节

  如下图示,起始位位于第3字节,停止位位于第0字节,那么输出给用户的最后一个数据所有字节均有效,仿真结果正确。

在这里插入图片描述

图15 起始位位于第3字节,停止位位于第0字节

  如下图示,起始位位于第3字节,停止位位于第1字节,那么输出给用户的最后一个数据只有最高字节数据有效,仿真结果正确。

在这里插入图片描述

图16 起始位位于第3字节,停止位位于第1字节

  如下图示,起始位位于第3字节,停止位位于第2字节,那么输出给用户的最后一个数据的高两字节数据均有效,仿真结果正确。

在这里插入图片描述

图17 起始位位于第3字节,停止位位于第2字节

  如下图示,起始位位于第3字节,停止位位于第3字节,那么输出给用户的最后一个数据的高三字节数据均有效,仿真结果正确。

在这里插入图片描述

图18 起始位位于第3字节,停止位位于第3字节

  仿真到此结束,通过修改TestBench中的TX_KEEP和SOF_LOCAL参数,完成了十六种接收格式的仿真,输出给用户的数据均没有问题,仿真结束。

  本模块主要完成输出数据的检测、字对齐、掩码信号的生成,支持接收发送端的任意字节数据。

  PHY顶层模块对应的RTL视图如下所示,由于高速收发器是全双工通信,因此把发送和接收分为两个模块。

在这里插入图片描述

图19 PHY顶层模块

  关于自定义PHY接收模块的设计到此结束了,下文将GT收发器模块、自定义PH发送模块、自定义PHY接收模块综合,然后通过光口对协议进行验证。


  如果对文章内容理解有疑惑或者对代码不理解,可以在评论区或者后台留言,看到后均会回复!

  如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!您的支持是我更新的最大动力!将持续更新工程!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微服务:Rabbitmq的基本的消息队列的入门简单使用(消息队列中间件)

先介绍最简单的使用方式&#xff0c;后面还会更新其他使用方法。 简单案例 目录结构 引入依赖&#xff1a; <!--AMQP依赖&#xff0c;包含RabbitMQ--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-star…

JAVA:Spring Boot整合Kaptcha验证码实现登录验证

请关注微信公众号&#xff1a;拾荒的小海螺 博客地址&#xff1a;http://lsk-ww.cn/ 1、简述 在Web应用程序中&#xff0c;验证码是一种常见的安全措施&#xff0c;用于验证用户的身份以防止恶意活动&#xff0c;如自动化攻击或机器人。Spring Boot提供了许多库和工具&#x…

UnityAPI学习之Transform组件基本使用

目录 Transform组件 访问与获取 Transform的位置和旋转信息 Transform局部坐标和旋转信息的获取 Transform的缩放与正方向 缩放&#xff08;Scale&#xff09; 正方向 Transform相关的查找方法 销毁游戏物体 Transform组件 访问与获取 现在创建一个容器放置GrisGO物…

VueX核心内容

聚沙成塔每天进步一点点 本文内容 ⭐ 专栏简介Vuex 核心内容核心概念1. State&#xff08;状态&#xff09;示例&#xff1a; 2. Getter&#xff08;获取器&#xff09;示例&#xff1a; 3. Mutation&#xff08;突变&#xff09;示例&#xff1a; 4. Action&#xff08;动作&a…

MbedTLS源码跨平台编译(window/macos/linux)

1.window平台编译: 克隆: git clone --recursive https://github.com/Mbed-TLS/mbedtls.git 克隆成功 添加OpenSSL环境变量 验证环境 使用cmake编译 cmake ../生成配置时出错 出现上面原因是克隆下来的library与programs及tests目录少文件了,直接下载zip包替换library目录

dibbler-DHCPv6 的开源框架(C++ 实现)1

一、下载 IPv6 DHCPv6 协议的开源框架&#xff1a;dibbler 下载地址&#xff1a;https://github.com/tomaszmrugalski/dibbler.git 二、代码编写语言和文件结构 编写语言 文件 三、编译 编译 server 端&#xff1a; chmod x configure ./configure# 编译服务端(4核) mak…

Renesas MCU之使用e² studio搭建开发环境

目录 概述 1 e studio介绍 2 搭建Renesas MUC开发环境 2.1 软件版本信息 2.2 安装软件 3 创建工程 3.1 板卡硬件接口 3.2 FSP配置IO 4 Generate Project 4.1 项目目录介绍 4.2 LED接口相关驱动 5 调试 5.1 测试代码 5.2 J-Link调试代码 5.3 硬件结构 概述 本文主…

【ARM】

ARM ■ 指令集■ 1. RISC■ 2. CISC ■ ARM简介■ 1.■ 2. ■ ARM-CPU体系架构■ 1. M0■ 2. M3■ 3. M4■ 4. M7■ 5. M7■ 6. M7 ■ ARM-寄存器■ 1. 通用寄存器■ 2.■ 3.■ 4. ■ ARM-工作模式■ ARM-寄存器组■ ARM-异常向量表■ 由于soc0x00000000 是存放IROM芯片出厂数据…

由MapTile引发的ResultSet的思考及实践

其实这篇文章应该是上周末来写的&#xff0c;但是苦逼啊。别人都抱怨工作996&#xff0c;我特么直接9117了&#xff0c;连轴转12天&#xff0c;完全没有个人时间&#xff0c;苦逼啊&#xff01; 本来周末计划看完龙珠Z&#xff08;日语&#xff09;布欧篇 呢&#xff0c;给自己…

喵星人必备!福派斯三文鱼猫粮,营养满分!

猫粮品牌&#xff1a;福派斯三文鱼猫粮测评体验 在快节奏的都市生活中&#xff0c;我们的宠物猫也需要适应当下的生活环境&#xff0c;并保持健康和活力。作为一名合格的铲屎官&#xff0c;我们总是关心如何为猫咪提供既健康又美味的饮食。今天&#xff0c;我有幸为大家带来一…

Gradle如何发布一个Android开源框架到JitPack

序言 在Android领域耕耘了多年的老司机们&#xff0c;技术大多已经沉淀到足以自己写各种各样的框架了。你有没有想过?其实你可以将写好的框架开源出来&#xff0c;让更多人受益&#xff0c;提升开发效率。亦或者是引用过别人com.github打头的开源框架&#xff0c;但是不知道自…

(一)django目录介绍

1、生成django项目&#xff0c;得到的目录如下 manage.py&#xff1a;命令行工具&#xff0c;内置多种方式与项目进行交互。在命令提示符窗口下&#xff0c;将路径切换到项目并输入python manage.py help,可以查看该工具的指令信息。 默认的数据库工具&#xff0c;sqlite 在…

SQL注入-时间盲注

SQL时间盲注&#xff08;Time-based Blind SQL Injection&#xff09;&#xff0c;又叫延时注入&#xff0c;是一种SQL注入攻击技术&#xff0c;用于在无法直接获取查询结果或查看响应内容变化的情况下&#xff0c;通过引入时间延迟来推断数据库的信息&#xff1b;时间盲注依赖…

UE 熟悉引擎

项目文件 模式&#xff1a; 参考视频&#xff1a;【基础】01课&#xff1a;创建项目全流程和模板试玩_哔哩哔哩_bilibili

YOLOv1深入解析与实战:目标检测算法原理

参考&#xff1a; https://zhuanlan.zhihu.com/p/667046384 https://blog.csdn.net/weixin_41424926/article/details/105383064 https://arxiv.org/pdf/1506.02640 1. 算法介绍 学习目标检测算法&#xff0c;yolov1是必看内容&#xff0c;不同于生成模型&#xff0c;没有特别…

计算机毕业设计 | 基于Koa+vue的高校宿舍管理系统宿舍可视化系统

项目介绍 项目背景 随着科技的发展&#xff0c;智能化管理越来越重要。大学生在宿舍的时间超过了1/3&#xff0c;因此良好的宿舍管理对学生的生活和学习极为关键。学生宿舍管理系统能够合理安排新生分配宿舍&#xff0c;不浪费公共资源&#xff0c;减轻学校管理压力&#xff…

[极速版]写个linux探测自己机器ip地址的tool(基于shell + sshpass)

背景&#xff1a;那个房间没有能正常上广域网的网口&#xff0c;就用了个无线中继 适用情况&#xff1a;上级路由ssh or teamviewer访问下级路由的机器&#xff0c;但下级路由不支持查看IP 自行完成下级路由&#xff08;此处指无线中继&#xff09;的端口映射or DMZ整机映射 a…

Codeforces Round 949 D. Turtle and Multiplication 【欧拉路径】

题意 要求构造一个长度为 n n n 的序列 a a a&#xff0c;使得&#xff1a; ∀ i ∈ [ 1 , n ] , 1 ≤ a i ≤ 3 ⋅ 1 0 5 \forall i \in [1,n], \; 1 \leq a_i \leq 3 \cdot 10^5 ∀i∈[1,n],1≤ai​≤3⋅105 ∀ 1 ≤ i < j ≤ n − 1 , a i ⋅ a i 1 ≠ a j ⋅ a j 1…

Java筑基-面向对象

Java-面向对象 一、类和对象1、类和对象的关系2、创建类3、创建对象4、成员变量与局部变量5、构造器5.1、创建对象的过程5.2、构造器的格式5.3、构造器和方法的区别5.4、构造器的作用5.5、构造器的重载 6、this关键字用法&#xff1a;6.1、this可以修饰属性6.2、this可以修饰方…

每日一题——Python实现PAT甲级1046 Shortest Distance(举一反三+思想解读+逐步优化)

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 我的写法 专业点评 优点 改进建议 时间复杂度分析 空间复杂度分析 总结 我要更…