基于GTX的64B66B编码的自定义协议发送模块(高速收发器二十一)

news2024/11/19 9:32:21

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


1、64B66B组帧原理

  前文讲解64B66B编码原理时,已经讲解过组帧的原理,包括数据帧和控制帧两种,区别在于同步码不同。

  下图是802.3的以太网控制协议,其中S表示起始位,T表示停止位。为了实现传输任意字节的数据,停止位有8种情况,根据控制帧的第一字节数据的值确定该控制帧的具体类型。

在这里插入图片描述

图1 64B/66B块格式

  例如同步头为2’b10表示该帧为控制帧,如果第一字节数据为8’haa,则表示此帧数据包含2字节有效数据。本文以图1的规则来实现64B66B的自定义PHY层,达到掌握GTX的64B66B编码开发的目的。

2、整理高速收发器

  由于官方的示例工程设计的比较繁琐,不便于扩展,因此在拿到示例工程之后,首先需要整理示例工程,将共享逻辑放到高速收发器的顶层模块,便于后续扩展高速收发器。

  整理后的高速收发器通道代码如下所示,主要包含用户时钟生成模块、DPLL复位模块、GTX IP三部分,与8B10B整理的高速收发器通道模块类似,参考代码如下所示。

    //例化时钟模块;
    gtwizard_0_GT_USRCLK_SOURCE u_gtwizard_0_GT_USRCLK_SOURCE(
        .GT0_TXUSRCLK_OUT       ( tx_usrclk     ),
        .GT0_TXUSRCLK2_OUT      ( tx_usrclk2    ),
        .GT0_TXOUTCLK_IN        ( tx_outclk     ),
        .GT0_TXCLK_LOCK_OUT     ( tx_mmcm_lock  ),
        .GT0_TX_MMCM_RESET_IN   ( tx_mmcm_reset ),
        .GT0_RXUSRCLK_OUT       ( rx_usrclk     ),
        .GT0_RXUSRCLK2_OUT      ( rx_usrclk2    ),
        .GT0_RXCLK_LOCK_OUT     ( rx_mmcm_lock  ),
        .GT0_RX_MMCM_RESET_IN   ( rx_mmcm_reset ) 
    );
    
    //例化复位同步模块;
    gtwizard_0_common_reset  #(
        .STABLE_CLOCK_PERIOD( 8 )//稳定时钟驱动状态机的周期,单位纳秒。
    )
    u_gtwizard_0_common_reset(    
        .STABLE_CLOCK       ( sysclk        ),//Stable Clock, either a stable clock from the PCB
        .SOFT_RESET         ( tx_rst        ),//User Reset, can be pulled any time
        .COMMON_RESET       ( commonreset   ) //Reset QPLL
    );

    //例化GTX IP
    gtwizard_0  u_gtwizard_0(
        .sysclk_in                      ( sysclk            ),//系统时钟信号;
        .soft_reset_tx_in               ( tx_rst            ),//发动通道的复位信号;
        .soft_reset_rx_in               ( rx_rst            ),//接收通道的复位信号;
        .dont_reset_on_data_error_in    ( 1'b0              ),//输入数据错误时不复位;
        .gt0_tx_fsm_reset_done_out      (                   ),//发送通道初始化复位成功;
        .gt0_rx_fsm_reset_done_out      ( rx_done           ),//接收通道初始化复位成功;
        .gt0_data_valid_in              ( rx_data_ready     ),//高电平表示GT输出给用户的数据正确;
        .gt0_tx_mmcm_lock_in            ( tx_mmcm_lock      ),//发送通道MMCM的锁定信号;
        .gt0_tx_mmcm_reset_out          ( tx_mmcm_reset     ),//发送通道MMCM的复位信号;
        .gt0_rx_mmcm_lock_in            ( rx_mmcm_lock      ),//接收通道MMCM的锁定信号;
        .gt0_rx_mmcm_reset_out          ( rx_mmcm_reset     ),//接收通道MMCM的复位信号;
        .gt0_drpaddr_in                 ( drpaddr           ),//DRP地址信号;
        .gt0_drpclk_in                  ( drpclk            ),//DRP时钟信号;
        .gt0_drpdi_in                   ( drpdi             ),//DRP输入数据信号;
        .gt0_drpdo_out                  ( drpdo             ),//DRP输出数据信号;
        .gt0_drpen_in                   ( drpen             ),//DRP使能信号;
        .gt0_drprdy_out                 ( drprdy            ),//DRP空闲信号;
        .gt0_drpwe_in                   ( drpwe             ),//DRP读写使能信号;
        .gt0_dmonitorout_out            (                   ),//监控输出端口;
        .gt0_loopback_in                ( loopback          ),//回环模式控制信号;
        .gt0_eyescanreset_in            ( 1'b0              ),//眼图扫描复位信号;
        .gt0_rxuserrdy_in               ( 1'b1              ),//用户接收端口准备好指示信号;
        .gt0_eyescandataerror_out       (                   ),//眼图相关信号;
        .gt0_eyescantrigger_in          ( 1'b0              ),//眼图相关信号;
        .gt0_rxclkcorcnt_out            (                   ),//output wire [1:0] gt0_rxclkcorcnt_out
        .gt0_rxusrclk_in                ( rx_usrclk         ),//接收通道PCS并行数据对应时钟;
        .gt0_rxusrclk2_in               ( rx_usrclk2        ),//接收通道用户接口时钟信号;
        .gt0_rxdata_out                 ( rx_data           ),//接收数据;
        .gt0_gtxrxp_in                  ( gtx_rx_p          ),//GTX的接收差分引脚;
        .gt0_gtxrxn_in                  ( gtx_rx_n          ),//GTX的接收差分引脚;
        .gt0_rxdfelpmreset_in           ( 1'b0              ),//LPM复位信号;
        .gt0_rxmonitorout_out           (                   ),//output wire [6:0] gt0_rxmonitorout_out
        .gt0_rxmonitorsel_in            ( 2'd0              ),//input wire [1:0] gt0_rxmonitorsel_in
        .gt0_rxoutclkfabric_out         (                   ),//output wire gt0_rxoutclkfabric_out
        .gt0_rxdatavalid_out            ( rx_data_vld       ),//接收数据有效指示信号;
        .gt0_rxheader_out               ( rx_header         ),//接收数据的帧头数据;
        .gt0_rxheadervalid_out          ( rx_header_vld     ),//接收数据的帧头数据有效指示信号;
        .gt0_rxgearboxslip_in           ( rx_slip           ),//手动字节对齐的滑块信号;
        .gt0_gtrxreset_in               ( rx_rst            ),//接收通道的复位信号;
        .gt0_rxpmareset_in              ( 1'b0              ),//接收通道PMA部分的复位信号;
        .gt0_rxpolarity_in              ( rx_polarity       ),//接收通道的极性翻转信号;
        .gt0_rxresetdone_out            (                   ),//接收通道复位完成指示信号;
        .gt0_txpostcursor_in            ( tx_postcursor     ),//发送通道预加重设置;
        .gt0_txprecursor_in             ( tx_precursor      ),//发送通道去加重设置;
        .gt0_gttxreset_in               ( tx_rst            ),//发送通道复位信号;
        .gt0_txuserrdy_in               ( 1'b1              ),//发送通道用户准备好指示信号;
        .gt0_txusrclk_in                ( tx_usrclk         ),//发送通道的PCS并行数据时钟信号;
        .gt0_txusrclk2_in               ( tx_usrclk2        ),//发送通道用户接口时钟信号;
        .gt0_txdiffctrl_in              ( tx_diffctrl       ),//发送通道的幅值调节;
        .gt0_txdata_in                  ( tx_data           ),//用户发送数据;
        .gt0_gtxtxn_out                 ( gtx_tx_n          ),//发送差分引脚;
        .gt0_gtxtxp_out                 ( gtx_tx_p          ),//发送差分引脚;
        .gt0_txoutclk_out               ( tx_outclk         ),//IP输出的时钟信号,通过MMCM生成用户接口时钟;
        .gt0_txoutclkfabric_out         (                   ),//output wire gt0_txoutclkfabric_out
        .gt0_txoutclkpcs_out            (                   ),//PCS时钟输出;
        .gt0_txheader_in                ( tx_header         ),//用户发送数据头部2字节数据;
        .gt0_txsequence_in              ( tx_sequence       ),//外部计数器输入数值;
        .gt0_txresetdone_out            ( tx_done           ),//发送通道复位完成指示信号;
        .gt0_txpolarity_in              ( tx_polarity       ),//发送通道极性控制信号;
        .gt0_qplllock_in                ( qplllock          ),//QPLL输出时钟锁定信号;
        .gt0_qpllrefclklost_in          ( qpllrefclklost    ),//QPLL参考时钟失锁信号;
        .gt0_qpllreset_out              ( qpllreset         ),//QPLL复位信号;
        .gt0_qplloutclk_in              ( qplloutclk        ),//QPLL输出时钟信号;
        .gt0_qplloutrefclk_in           ( qplloutrefclk     ) //QPLL参考时钟信号;
    );

在这里插入图片描述

图2 整理后高速收发器的模块分布

  本文使用两个高速收发器,通过光口后回环数据,因此在高速收发器的顶层模块会例化两个上述整理的模块。

  顶层模块还包含IBUFDS_GTE2将差分参考时钟转换为单端时钟信号,之后通过GT_COMMON调用QPLL生成GTX需要的时钟信号,参考代码如下所示。

    //例化高速收发器的一个通道;
    gt_channel u_gt_channel_0(
        .sysclk         ( sysclk            ),//系统时钟信号;
        .tx_rst         ( tx_rst_0          ),//发动通道的复位信号;
        .rx_rst         ( rx_rst_0          ),//接收通道的复位信号;
        .tx_done        ( tx_done_0         ),//发送通道复位完成指示信号;
        .rx_done        ( rx_done_0         ),//接收通道初始化复位成功;
        .rx_data_ready  ( rx_data_ready_0   ),//高电平表示GT输出给用户的数据正确;
        .drpaddr        ( drpaddr_0         ),//DRP地址信号;
        .drpclk         ( drpclk_0          ),//DRP时钟信号;
        .drpdi          ( drpdi_0           ),//DRP输入数据信号;
        .drpdo          ( drpdo_0           ),//DRP输出数据信号;
        .drpen          ( drpen_0           ),//DRP使能信号;
        .drprdy         ( drprdy_0          ),//DRP空闲信号;
        .drpwe          ( drpwe_0           ),//DRP读写使能信号;
        .loopback       ( loopback_0        ),//回环模式控制信号;
        .rx_clk         ( rx_clk_0          ),//接收通道用户接口时钟信号;
        .rx_data        ( rx_data_0         ),//接收数据;
        .gtx_rx_p       ( gtx_rx_p_0        ),//GTX的接收差分引脚;
        .gtx_rx_n       ( gtx_rx_n_0        ),//GTX的接收差分引脚;
        .rx_data_vld    ( rx_data_vld_0     ),//接收数据有效指示信号;
        .rx_header      ( rx_header_0       ),//接收数据的帧头数据;
        .rx_header_vld  ( rx_header_vld_0   ),//接收数据的帧头数据有效指示信号;
        .rx_slip        ( rx_slip_0         ),//手动字节对齐的滑块信号;
        .rx_polarity    ( rx_polarity_0     ),//接收通道的极性翻转信号;
        .tx_postcursor  ( tx_postcursor_0   ),//发送通道预加重设置;
        .tx_precursor   ( tx_precursor_0    ),//发送通道去加重设置;
        .tx_clk         ( tx_clk_0          ),//发送通道用户接口时钟信号;
        .tx_diffctrl    ( tx_diffctrl_0     ),//发送通道的幅值调节;
        .tx_data        ( tx_data_0         ),//用户发送数据;
        .gtx_tx_n       ( gtx_tx_n_0        ),//发送差分引脚;
        .gtx_tx_p       ( gtx_tx_p_0        ),//发送差分引脚;
        .tx_header      ( tx_header_0       ),//用户发送数据头部2字节数据;
        .tx_sequence    ( tx_sequence_0     ),//外部计数器输入数值;
        .tx_polarity    ( tx_polarity_0     ),//发送通道极性控制信号;
        .qplllock       ( qplllock          ),//QPLL输出时钟锁定信号;
        .qpllrefclklost ( qpllrefclklost    ),//QPLL参考时钟失锁信号;
        .qpllrst        ( qpllrst           ),//QPLL复位信号;
        .qplloutclk     ( qplloutclk        ),//QPLL输出时钟信号;
        .qplloutrefclk  ( qplloutrefclk     ) //QPLL参考时钟信号;
    );

    //例化高速收发器的一个通道;
    gt_channel u_gt_channel_1(
        .sysclk         ( sysclk            ),//系统时钟信号;
        .tx_rst         ( tx_rst_1          ),//发动通道的复位信号;
        .rx_rst         ( rx_rst_1          ),//接收通道的复位信号;
        .tx_done        ( tx_done_1         ),//发送通道复位完成指示信号;
        .rx_done        ( rx_done_1         ),//接收通道初始化复位成功;
        .rx_data_ready  ( rx_data_ready_1   ),//高电平表示GT输出给用户的数据正确;
        .drpaddr        ( drpaddr_1         ),//DRP地址信号;
        .drpclk         ( drpclk_1          ),//DRP时钟信号;
        .drpdi          ( drpdi_1           ),//DRP输入数据信号;
        .drpdo          ( drpdo_1           ),//DRP输出数据信号;
        .drpen          ( drpen_1           ),//DRP使能信号;
        .drprdy         ( drprdy_1          ),//DRP空闲信号;
        .drpwe          ( drpwe_1           ),//DRP读写使能信号;
        .loopback       ( loopback_1        ),//回环模式控制信号;
        .rx_clk         ( rx_clk_1          ),//接收通道用户接口时钟信号;
        .rx_data        ( rx_data_1         ),//接收数据;
        .gtx_rx_p       ( gtx_rx_p_1        ),//GTX的接收差分引脚;
        .gtx_rx_n       ( gtx_rx_n_1        ),//GTX的接收差分引脚;
        .rx_data_vld    ( rx_data_vld_1     ),//接收数据有效指示信号;
        .rx_header      ( rx_header_1       ),//接收数据的帧头数据;
        .rx_header_vld  ( rx_header_vld_1   ),//接收数据的帧头数据有效指示信号;
        .rx_slip        ( rx_slip_1         ),//手动字节对齐的滑块信号;
        .rx_polarity    ( rx_polarity_1     ),//接收通道的极性翻转信号;
        .tx_postcursor  ( tx_postcursor_1   ),//发送通道预加重设置;
        .tx_precursor   ( tx_precursor_1    ),//发送通道去加重设置;
        .tx_clk         ( tx_clk_1          ),//发送通道用户接口时钟信号;
        .tx_diffctrl    ( tx_diffctrl_1     ),//发送通道的幅值调节;
        .tx_data        ( tx_data_1         ),//用户发送数据;
        .gtx_tx_n       ( gtx_tx_n_1        ),//发送差分引脚;
        .gtx_tx_p       ( gtx_tx_p_1        ),//发送差分引脚;
        .tx_header      ( tx_header_1       ),//用户发送数据头部2字节数据;
        .tx_sequence    ( tx_sequence_1     ),//外部计数器输入数值;
        .tx_polarity    ( tx_polarity_1     ),//发送通道极性控制信号;
        .qplllock       ( qplllock          ),//QPLL输出时钟锁定信号;
        .qpllrefclklost ( qpllrefclklost    ),//QPLL参考时钟失锁信号;
        .qpllrst        (                   ),//QPLL复位信号;
        .qplloutclk     ( qplloutclk        ),//QPLL输出时钟信号;
        .qplloutrefclk  ( qplloutrefclk     ) //QPLL参考时钟信号;
    );

  整理后的高速收发器顶层模块RTL视图如下图所示,内部包含两个高速收发器。注意只有第一个高速收发器才能复位QPLL。

在这里插入图片描述

图3 高速收发器顶层模块RTL视图

  整理过程与8B10B编码时基本一致,为了缩小篇幅,此处省略,如果不熟悉可以对照官方的示例工程进行整理。

  本文不对整理后的模块仿真,在将上层自定义的收发模块设计完成后,在同时进行仿真即可。

3、自定义PHY发送模块设计

  开放给用户的端口设计为axi_stream,内部采用FIFO将用户需要发送的数据暂存(此处也可以采用移位寄存器暂存,会更简单)。当接收到用户数据后,开始组帧,并从FIFO中读出数据发送,直到把一次写入FIFO的数据全部读出为止。同时向GTX输出数据(tx_data),同步头(tx_header),外部计数器(tx_sequence)三个信号。

  首先写使能在FIFO未满且用户写入数据时拉高,由于用户接口采用大端对齐,而GTX的数据是小端对齐,因此将用户数据大小端转换后写入FIFO中。同时需要使用计数器tx_data_len对输入数据的长度进行计数,之后从FIFO中读取相应数据进行组帧。另外需要保存用户输入数据的尾端掩码信号,便于生成组帧的最后一个数据的控制码。

  需要一个32进制的计数器tx_sequence_cnt来生成tx_sequence,且控制输出数据和同步头。

  将用户输入有效数据的上升沿作为组帧起始标志,同时计数器tx_cnt用于计数发送数据的个数,由于用户发送最后一个数据的掩码信号会影响组帧的长度,因此计数器的最大值根据最后一个数据的掩码状态会发生变化。使用tx_cnt_num表示不同尾端掩码时计数器tx_cnt的最大值,从而确定组帧的长度。

//发送数据个数计数器,用于记录FIFO读出数据个数;
    always@(posedge tx_clk)begin
        if(tx_rst)begin//
            tx_cnt <= 0;
        end
        else if(add_tx_cnt)begin
            if(end_tx_cnt)
                tx_cnt <= 0;
            else
                tx_cnt <= tx_cnt + 1;
        end
    end
    
    assign add_tx_cnt = tx_flag && gt_tx_data_vld;//当从FIFO读取数据且处于发送数据状态时加1.
    assign end_tx_cnt = add_tx_cnt && (tx_cnt == tx_cnt_num);

    assign fifo_rd_en = add_tx_cnt;//将计数器加一条件用作FIFO读使能信号。

    //生成计数器的最大值;
    always@(posedge tx_clk)begin
        if(tx_rst)begin//初始值为最大值;
            tx_cnt_num <= 'd511;
        end
        else if(end_tx_cnt)begin//当发送一次数据后,将该值设置为最大值,防止下次发送数据时提前结束。
            tx_cnt_num <= 'd511;
        end
        else if(s_axi_last_r && s_axi_valid_r)begin//通过一帧数据最后一个状态确定计数器的最大值。
            if((s_axi_keep_r == 8'b1111_1110) || (s_axi_keep_r == 8'b1111_1111))begin
                tx_cnt_num <= tx_data_len;
            end
            else begin
                tx_cnt_num <= tx_data_len - 1;
            end
        end
    end

  然后就是生成输出数据,如下所示,根据图1和用户尾端数据掩码的状态生成组帧的停止位。在组帧开始时发送起始帧,其余发送数据时间应该将前后两个数据的部分进行组合,参考代码如下。

//生成输出数据;
    always@(posedge tx_clk)begin
        if(tx_rst)begin//初始值为0;
            tx_data <= 'd0;
        end
        else if(end_tx_cnt)begin//一帧最后一个数据的处理;
            case (s_axi_keep_r)
                8'b1000_0000 : tx_data <= {7'h07,7'h07,7'h07,7'h07,7'h07,5'd0,fifo_dout[7:0],fifo_dout_r[63:56],8'haa};
                8'b1100_0000 : tx_data <= {7'h07,7'h07,7'h07,7'h07,4'd0,fifo_dout[15:0],fifo_dout_r[63:56],8'hb4};
                8'b1110_0000 : tx_data <= {7'h07,7'h07,7'h07,3'd0,fifo_dout[23:0],fifo_dout_r[63:56],8'hcc};
                8'b1111_0000 : tx_data <= {7'h07,7'h07,2'd0,fifo_dout[31:0],fifo_dout_r[63:56],8'hd2};
                8'b1111_1000 : tx_data <= {7'h07,1'd0,fifo_dout[39:0],fifo_dout_r[63:56],8'he1};
                8'b1111_1100 : tx_data <= {fifo_dout[55:0],fifo_dout_r[63:56],8'hff};
                8'b1111_1110 : tx_data <= {7'h07,7'h07,7'h07,7'h07,7'h07,7'h07,7'h07,7'd07,8'h87};
                8'b1111_1111 : tx_data <= {7'h07,7'h07,7'h07,7'h07,7'h07,7'h07,6'd0,fifo_dout_r[63:56],8'h99};
                default: ;
            endcase
        end
        else if(tx_flag)begin
            if(add_tx_cnt && tx_cnt == 0)//一帧开头数据的处理方式;
                tx_data <= {fifo_dout[55:0],8'h78};
            else if(add_tx_cnt)
                tx_data <= {fifo_dout[55:0],fifo_dout_r[63:56]};//一帧数据中间的处理方式;
        end
        else begin
            tx_data <= 64'h07070707_07070707;//其余时间发送空闲数据;
        end
    end

  在开始和停止发送数据时,都是发送的控制帧,因此此时的同步头应该输出2’b10,其余时间均发送数据帧,同步头为2’b01。

//发送头部数据;
    always@(posedge tx_clk)begin
        if(tx_rst)begin//初始值为2'b01,表示发送的是数据帧;
            tx_header <= 2'b01;
        end
        else if(end_tx_cnt || (add_tx_cnt && tx_cnt == 0))begin//发送帧头和帧尾时为控制数据;
            tx_header <= 2'b10;
        end
        else begin//其余时间为2'b01,表示数据帧。
            tx_header <= 2'b01;
        end
    end

4、仿真自定义PHY发送模块

  该模块对应的TestBench如下所示,通过修改TX_KEEP参数更改尾端的数据掩码信号。

`timescale 1 ns/1 ns
module tb_phy_tx();
    localparam	CYCLE		= 	10		        ;//系统时钟周期,单位ns,默认10ns;
    localparam	RST_TIME	= 	10		        ;//系统复位持续时间,默认10个系统时钟周期;
    localparam  TX_KEEP     =   8'b1111_1111    ;//发送最后一个数据的有效位数,大端对齐;

    reg			                clk             ;//系统时钟,默认100MHz;
    reg			                rst_n           ;//系统复位,默认低电平有效;
    reg         [4 : 0]         send_value      ;

    reg                         s_axi_valid     ;//数据有效指示信号,高电平有效;
    reg                         s_axi_last      ;//帧结束指示信号,高电平有效;
    reg         [63 : 0]        s_axi_data      ;//数据信号;
    reg         [7 : 0]         s_axi_keep      ;//数据掩码信号;

    wire                        s_axi_ready     ;//接收数据应答信号;
    wire        [63 : 0]        tx_data         ;//GTX需要发送的数据;
    wire        [1 : 0]         tx_header       ;//GTX需要发送的头部数据;
    wire        [6 : 0]         tx_sequence     ;//GTX外部计数器;

    phy_tx u_phy_tx(
        .tx_clk         ( clk           ),//系统时钟信号;
        .tx_rst         ( ~rst_n        ),//系统复位信号,高电平有效;
        .s_axi_valid    ( s_axi_valid   ),//数据有效指示信号,高电平有效;
        .s_axi_last     ( s_axi_last    ),//帧结束指示信号,高电平有效;
        .s_axi_data     ( s_axi_data    ),//数据信号;
        .s_axi_keep     ( s_axi_keep    ),//数据掩码信号;
        .s_axi_ready    ( s_axi_ready   ),//接收数据应答信号;
        .tx_data        ( tx_data       ),//GTX需要发送的数据;
        .tx_header      ( tx_header     ),//GTX需要发送的头部数据;
        .tx_sequence    ( tx_sequence   ) //GTX外部计数器;
    );

    //生成周期为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
        s_axi_data  = 64'd0;
        s_axi_keep  = 8'd0;
        s_axi_last  = 1'd0;
        s_axi_valid = 1'd0;
        wait(rst_n);//等待复位完成;
        repeat(10) @(posedge clk);
        repeat(5) begin
            phy_tx_task(5);
        end
        @(posedge s_axi_ready);
        $stop;
    end

    //发送数据的任务;
    task phy_tx_task(
        input	[8 : 0]		len
    );
        begin : phy_tx_task_0
            integer i;
            s_axi_data  <= 64'd0;
            s_axi_keep  <= 8'd0;
            s_axi_last  <= 1'd0;
            s_axi_valid <= 1'd0;
            send_value <= 5'd1;
            @(posedge clk);
            wait(s_axi_ready);
            @(posedge clk);
            for(i=0 ; i<len ; i=i+1)begin
                s_axi_data <= {{send_value[4:0],3'd0},{send_value[4:0],3'd1},{send_value[4:0],3'd2},{send_value[4:0],3'd3},{send_value[4:0],3'd4},{send_value[4:0],3'd5},{send_value[4:0],3'd6},{send_value[4:0],3'd7}};
                if(i == len - 1)begin//最后一个数据时控制掩码信号;
                    s_axi_last <= 1'b1;
                    s_axi_keep <= TX_KEEP;
                end
                else begin
                    s_axi_last <= 1'b0;
                    s_axi_keep <= 8'hff;
                end
                s_axi_valid <= 1'b1;
                send_value <= send_value + 1;
                @(posedge clk);
            end
            s_axi_data  <= 64'd0;
            s_axi_keep  <= 8'd0;
            s_axi_last  <= 1'd0;
            s_axi_valid <= 1'd0;
            @(posedge clk);
        end
    endtask

endmodule

  下图用户一帧发送5个数据,尾端掩码为8’h80,最后一个有效字节数据为8’h28。

在这里插入图片描述

图4 仿真用户数据时序

  最终组帧输出时序如下图所示,起始控制帧发送的第一个字节为8‘h78,与图1中的起始位对应。

  停止帧第一个字节数据为8’haa,表示存在两字节有效数据,因此发送的最后一个有效字节数据为8’h28,与上图用户输入的最后一个有效字节数据对应。

在这里插入图片描述

图5 仿真组帧时序

  修改用户的尾端数据掩码为8’hc0继续仿真,由于数据并没有发生变化,因此用户发送的最后一个有效数据为8’h29。

  组帧的仿真结果如下所示,停止帧的第一个字节数据为8’hb4,表示该帧存在三字节有效数据,因此组帧发送最后一个有效字节数据为8’h29,与用户发送数据对应。

在这里插入图片描述

图6 仿真组帧时序

  继续修改用户数据的尾端掩码信号为8’he0,得到组帧仿真结果如下。组帧停止位的第一字节为8’hcc,表示这帧存在4字节有效数据,发送的最后一字节数据为8’h2a,与用户发送数据一致。

在这里插入图片描述

图7 仿真组帧时序

  根据上述思路,需要对8种情况依次仿真,因为在设计时我已经全部仿真过了,后续只对最后一种情况仿真。如果用户有疑问,在后续拿到工程后可以自行仿真。

  将用户的尾端数据掩码信号设置为8’hFF,用户发送最后一个数据为8’h2f,然后运行仿真,得到组帧结果如下所示。

  组帧的最后一个数据的最低字节数据为8’h99,表示该帧数据只有一字节的有效数据,因此最后一个字节有效数据为8’h2f,与用户发送数据一致。

在这里插入图片描述

图8 仿真组帧时序

  通过上述仿真结果证明发送模块的逻辑设计没有问题,在后文接收模块设计完成后,在一起进行上板测试。

  64B66B编码的自定义协议相比8B10B编码会简单很多,8B10B需要用户完成字对齐,麻烦很多,如果前文自己完成过自定义8B10B编码的协议设计,那么64B66B自定义协议将没有难度。

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

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

相关文章

LINUX系统编程:多线程互斥

目录 1.铺垫 2.线程锁接口的认识 静态锁分配 动态锁的分配 互斥量的销毁 互斥量加锁和解锁 3.加锁版抢票 4.互斥的底层实现 1.铺垫 先提一个小场景&#xff0c;有1000张票&#xff0c;现在有4个进程&#xff0c;这四个进程疯狂的去抢这1000张票&#xff0c;看看会发生什…

熊猫烧香是什么?

熊猫烧香&#xff08;Worm.WhBoy.cw&#xff09;是一种由李俊制作的电脑病毒&#xff0c;于2006年底至2007年初在互联网上大规模爆发。这个病毒因其感染后的系统可执行文件图标会变成熊猫举着三根香的模样而得名。熊猫烧香病毒具有自动传播、自动感染硬盘的能力&#xff0c;以及…

简单爬虫案例——爬取快手视频

网址&#xff1a;aHR0cHM6Ly93d3cua3VhaXNob3UuY29tL3NlYXJjaC92aWRlbz9zZWFyY2hLZXk9JUU2JThCJTg5JUU5JTlEJUEy 找到视频接口&#xff1a; 视频链接在photourl中 完整代码&#xff1a; import requestsimport re url https://www.kuaishou.com/graphql cookies {did: web_…

C语言 | Leetcode C语言题解之第207题课程表

题目&#xff1a; 题解&#xff1a; bool canFinish(int numCourses, int** prerequisites, int prerequisitesSize, int* prerequisitesColSize) {int** edges (int**)malloc(sizeof(int*) * numCourses);for (int i 0; i < numCourses; i) {edges[i] (int*)malloc(0);…

一点连接千家银行,YonSuite让“银企对账”一键确认

在当今数智化浪潮下&#xff0c;成长型企业面临着前所未有的机遇与挑战。特别是在与银行的对接以及银企对账等方面&#xff0c;传统的手动操作模式已难以满足企业高效、安全的金融管理需求。用友YonSuite作为一款全场景SaaS应用服务&#xff0c;凭借其强大的银企直联功能&#…

【C++ 初阶路】--- C++内存管理

目录 一、C/C内存分布二、C内存管理方式2.1 new/delete操作内置类型2.2 new和delete操作自定义类型 三、operator new与operator delete函数四、new和delete的实现原理4.1 内置类型4.2 自定义类型 一、C/C内存分布 int globalVar 1; static int staticGlobalVar 1; void Tes…

树立行业标杆,林清轩获“以油养肤开创者”市场地位认证

从0到1的创造&#xff0c;才能快速实现从1到100的裂变&#xff0c;这是亘古不变的商业逻辑。 6月25日&#xff0c;知名美妆国货品牌林清轩&#xff0c;获得了CIC灼识的市场地位确认书&#xff0c;确定“以油养肤开创者” 的地位。 近两年&#xff0c;以油养肤的概念逐渐兴起&am…

【Python】入门Python,你必须了解这些事

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️Python】 文章目录 前言一、认识Python什么是Python&#xff1f;!Python的起源Python的特点简洁易读跨平台&#xff0c;可移植拥有强大的库和框架面向对象 Python的优缺点优点缺点 Python的应用环境扩…

【第六节】C/C++静态查找算法

目录 前言 一、搜索查找 二、查找算法 1. 线性查找&#xff08;Linear Search&#xff09; 2. 二分查找&#xff08;Binary Search&#xff09; 3. 插值查找&#xff08;Interpolation Search&#xff09; 4. 哈希查找&#xff08;Hash Search&#xff09; 5. Fibonacc…

气膜足球馆需要投资多少—轻空间

随着足球运动的普及和人们对健康生活方式的追求&#xff0c;建设高质量的足球场地成为许多城市和社区的需求。在众多建设方案中&#xff0c;气膜足球馆因其独特的优势&#xff0c;逐渐成为一种受欢迎的选择。轻空间将探讨建设气膜足球馆所需的投资情况&#xff0c;并分析其成本…

InnoDB 表空间2---系统表空间

系统表空间 了解完了独立表空间的基本结构&#xff0c;系统表空间的结构也就好理解多了&#xff0c;系统表空间的结构和独立表空间基本类似&#xff0c;只不过由于整个MySQL进程只有一个系统表空间&#xff0c;在系统表空间中会额外记录一些有关整个系统信息的页&#xff0c;所…

MySQL之主从同步、分库分表

1、主从同步的原理 MySQL主从复制的核心是二进制日志 二进制日志&#xff08;binlog&#xff09;记录了所有DDL语句和DML语句&#xff0c;但不包括数据查询&#xff08;select、show&#xff09;语句。 1.1、复制分三步 master主库在事务提交时&#xff0c;会把数据变更记录…

干货分享:Spring中经常使用的工具类(提示开发效率)

环境&#xff1a;Spring5.3…30 1、资源工具类 ResourceUtils将资源位置解析为文件系统中的文件的实用方法。 读取classpath下文件 File file ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX "logback.xml") ; // ...读取文件系统文件 file Resou…

淘系-万相台无界实操运营课:淘系 付费工具课(40节课)

课程目录 01_万相台无界系统性忖费推广思维.mp4 02_万相台无界七大推广场景详解.mp4 03关键词推广计划之标准计划搭建技巧.mp4 04_关键词推广之智能计划推广技巧.mp4 05_关键词推广之趋势选品计划推广技巧.mp4 06关键词推广之智能选品计划推广技巧.mp4 07_非标品的关键词…

做好准备了吗?智能手机又一轮涨价潮来了……

还记得十一二年前&#xff0c;智能手机刚大力普及的时候&#xff0c;以小米、魅族为首的新势力品牌&#xff0c;快速打破三星、苹果及HTC们的高价门槛&#xff0c;将旗舰产品的价格快速压到了两千价位。‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ 一时间&#xff0c;手机市场快速完成了…

鸿蒙:路由Router原理

页面路由&#xff1a;在应用程序中实现不同页面之间的跳转和数据传递 典型应用&#xff1a;商品信息返回、订单等多页面跳转 页面栈最大容量为32个页面&#xff0c;当页面需要销毁可以使用router.clear()方法清空页面栈 router有两种页面跳转模式&#xff1a; router.pushUrl…

压缩算法LZ4

LZ4简介 LZ4 是无损压缩算法&#xff0c;提供每个核 大于 500 MB/s 的压缩速度&#xff0c;可通过多核 CPU 进行扩展。LZ4算法解压速度极快&#xff0c;单核解压速度达到GB/s&#xff0c;通常达到多核系统的 RAM 速度限制。 压缩速度可以动态调整&#xff0c;选择一个“加速”…

VLOOKUP函数在表格的简单运用-两个表匹配

1.什么是VLOOKUP&#xff1f; VLOOKUP是Excel中的一个内置函数&#xff0c;主要用于在区域或表格的首列查找指定的值&#xff0c;并返回该行中其他列的值。它特别适用于跨表格数据匹配 2.函数运用 2.1.这边两个表取名a表和b表&#xff0c;做为我们的实例表。 表格a包含&…

windows环境下创建python虚拟环境

windows环境下创建python虚拟环境 使用virtualenv库创建虚拟环境&#xff0c;可使不同的项目处于不同的环境中 安装方法&#xff1a; pip install virtualenv -i https://pypi.tuna.tsinghua.edu.cn/simple pip install virtualenvwrapper-win -i https://pypi.tuna.tsinghua…

git 还原被删除的分支

在多人项目开发中&#xff0c;有一次碰到忘记合并到master分支了&#xff0c;直接就把开发分支给删除了&#xff0c;现在记录下怎么还原被删除的分支 必须保证删除的分支之前已经被推送到了远程仓库 # 找出被删除分支的最后一个提交的哈希值 git reflog show# 找到提交哈希值…