点击进入高速收发器系列文章导航界面
1、64B66B组帧原理
前文讲解64B66B编码原理时,已经讲解过组帧的原理,包括数据帧和控制帧两种,区别在于同步码不同。
下图是802.3的以太网控制协议,其中S表示起始位,T表示停止位。为了实现传输任意字节的数据,停止位有8种情况,根据控制帧的第一字节数据的值确定该控制帧的具体类型。
例如同步头为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参考时钟信号;
);
本文使用两个高速收发器,通过光口后回环数据,因此在高速收发器的顶层模块会例化两个上述整理的模块。
顶层模块还包含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。
整理过程与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。
最终组帧输出时序如下图所示,起始控制帧发送的第一个字节为8‘h78,与图1中的起始位对应。
停止帧第一个字节数据为8’haa,表示存在两字节有效数据,因此发送的最后一个有效字节数据为8’h28,与上图用户输入的最后一个有效字节数据对应。
修改用户的尾端数据掩码为8’hc0继续仿真,由于数据并没有发生变化,因此用户发送的最后一个有效数据为8’h29。
组帧的仿真结果如下所示,停止帧的第一个字节数据为8’hb4,表示该帧存在三字节有效数据,因此组帧发送最后一个有效字节数据为8’h29,与用户发送数据对应。
继续修改用户数据的尾端掩码信号为8’he0,得到组帧仿真结果如下。组帧停止位的第一字节为8’hcc,表示这帧存在4字节有效数据,发送的最后一字节数据为8’h2a,与用户发送数据一致。
根据上述思路,需要对8种情况依次仿真,因为在设计时我已经全部仿真过了,后续只对最后一种情况仿真。如果用户有疑问,在后续拿到工程后可以自行仿真。
将用户的尾端数据掩码信号设置为8’hFF,用户发送最后一个数据为8’h2f,然后运行仿真,得到组帧结果如下所示。
组帧的最后一个数据的最低字节数据为8’h99,表示该帧数据只有一字节的有效数据,因此最后一个字节有效数据为8’h2f,与用户发送数据一致。
通过上述仿真结果证明发送模块的逻辑设计没有问题,在后文接收模块设计完成后,在一起进行上板测试。
64B66B编码的自定义协议相比8B10B编码会简单很多,8B10B需要用户完成字对齐,麻烦很多,如果前文自己完成过自定义8B10B编码的协议设计,那么64B66B自定义协议将没有难度。