一天上手Aurora 8B/10B IP核(4)----从Streaming接口的官方例程学起

        Xilinx的技术生态做的非常好,基本上所有常用的IP核都有官方例程(example design)供开发者学习,咱不用白不用,今儿咱就一起白嫖他一手----从官方例程开始学习如何具体使用这个IP核。

        系列汇总:一天上手Aurora 8B/10B IP核----汇总篇(直达链接)


1、IP核定制与官方例程的生成

        首先新建一个工程,这个工程什么除了生成Aurora 8B/10B IP核以外什么也不做。

        IP核的定制过程如下:

1.1、第一页配置:物理层以及链路层信息选择

        

物理层Physical Layer:

        Lane Width:链路位宽,可选项2或者4,单位Bytes(这里我们为了将用户时钟降下来方便后续做时钟约束,选择4bytes)

        Line Rate:线速率,范围0.5~6.25Gbps,这个根据项目需求来就行,选择3.125Gbps

        GT Refclk:串行收发器GT的参考时钟,根据板卡实际情况来,我们这里选择125M

        INIT clk:初始化时钟,在GT复位时,user_clk是停止工作的,推荐INIT CLK时钟频率低于GT参考时钟,直接选择默认50M

        DRP clk:动态重配置时钟,该功能用的不多,直接按默认50M来就是的

链路层Link Layer:

        Dataflow Mode:数据流模式,可选全双工/ 只接收/ 只发送,我们这里选择全双工模式

        Interface:Framing/streaming可选。本文为streaming接口

        Flow Control:流控,streaming接口模式下不可选

        Back Channel:只有在单工模式才能选择(sidebands/timer 可选)

        Scrambler/Descrambler :绕码/解绕,这里不选择

        Little Endian Support :勾选小端模式,小段模式对应[31:0]这种书写习惯,大端模式对应的是[0:31]这种书写习惯

调试和控制:Debug And Control:

        提供了诸如ILA和VIO等调试IO核和其他一些状态指示位来对IP的运行状态进行监控,我们为了测试方便,这类调试接口暂时都不选取

1.2、第二页配置:对应GT收发器的物理位置选择

        这里根据自己的FPGA芯片上的GT实际情况选择就行,我们仅为仿真测试就随便选取一条通道:

1.3、第三页配置:共享逻辑的位置

        例如时钟以及复位等逻辑,是在核内还是在例子工程内(大多数IP核Xilinx都会提供例程供参考)。推荐将共享逻辑设置在官方例程内,这样我们后续对IP核的使用就可以直接在官方例程的基础上进行一点小修改就行了。

1.4、官方例程Example Design的生成

        将IP核定制好并综合后,就可以生成官方例程Example Design了:

2、官方例程解析

        对官方例程的解析主要参考生成源码以及《PG046:Aurora 8B/10B v11.1 LogiCORE IP Product Guide》。通过上述资料中,我们可以掌握IP核的使用方法和了解例程的大致组成。

2.1、官方例程的组成

        我们首先看下生成例程资源的逻辑层级排列:

        设计资源的层次这样看不是很明显,我们调出框图再来看看:

       

        这样基本能看明白大概了:

  • support模块:核心,包含了对IP、GT等的例化处理等一系列操作;后续在我们的应用中此部分不需要修改
  • frame_gen:数据生成模块,采用LFSR的方式生成伪随机序列;后续在我们的应用中此部分可替换成我们的数据输入模块(建议加入FIFO,这样代码的复用性更佳)
  • frame_check:数据检验模块,对接受的数据进行检验以验证传输的正确性;后续在我们的应用中此部分可替换成我们的数据检查模块或者删除
  • LL_AXI:LL总线转AXI总线(据说该例程原本的接口是LL接口,后面Xilinx为了推广AXI总线,所以本例程直接在原来代码的基础上增加了总线转换模块)
  • AXI_LL:AXI总线转LL总线,原因同上

        仿真部分的内容根据层级就很容易理解--调用了两次例程。根据Xilinx一贯的尿性,很容易猜出来这又是要进行环回测试。

        实际上从手册中也可以窥见一二:

  • Testbench例化了两个例程 
  • 例程1生成数据后经Aurora发给了例程2的检测模块,并将结果反馈给Testbench
  • 例程2生成数据后经Aurora发给了例程1的检测模块,并将结果反馈给Testbench

2.2、Support模块(IP核例化核心模块)

        废话不多说,先来看下Support模块的组成: 

         从组成就不难看出,Support模块最主要的功能是例化aurora IP核并将时钟、复位等信号统统一起打包。所以我们不需要对Support模块的内部构造进行详细了解,直接看看其对外接口就差不多可以拿来用了。

        Support模块下的子模块:

  • clock_module:时钟处理生成模块
  • support_reset_logic_i:复位逻辑生成模块
  • gt_common_support:与GT收发器相关的时钟生成其其他

2.3、数据生成模块(发送)

        上面说了只要将数据生成模块替换成我们自己的文件(可能还需要对数据检查模块(接收)进行更改)就可以实现对官方例程的二次创作,从而完成对Aurora IP的利用了。那么我们自然需要对其源码好好探究一番了。

        完整代码如下:

`timescale 1 ns / 1 ps
`define DLY #1

module aurora_8b10b_0_FRAME_GEN
(
    // User Interface
    TX_D, 
    TX_SRC_RDY_N,
    TX_DST_RDY_N,

    // System Interface
    USER_CLK,      
    RESET,
    CHANNEL_UP
);
//*****************************Parameter Declarations****************************

//***********************************Port Declarations*******************************

// User Interface
output  [0:31]     TX_D;
output             TX_SRC_RDY_N;
input              TX_DST_RDY_N;

// System Interface
input              USER_CLK;
input              RESET; 
input              CHANNEL_UP;

//***************************External Register Declarations***************************
reg                TX_SRC_RDY_N;

//***************************Internal Register Declarations***************************
reg     [0:15]     	data_lfsr_r;       
wire               	reset_c;
wire       			dly_data_xfer;
reg [4:0]  			channel_up_cnt;

//*********************************Main Body of Code**********************************

  always @ (posedge USER_CLK)
  begin
    if(RESET)
        channel_up_cnt <= `DLY 5'd0;
    else if(CHANNEL_UP)
      if(&channel_up_cnt)
        channel_up_cnt <= `DLY channel_up_cnt;
      else 
        channel_up_cnt <= `DLY channel_up_cnt + 1'b1;
    else
      channel_up_cnt <= `DLY 5'd0;
  end

  assign dly_data_xfer = (&channel_up_cnt);

  //Generate RESET signal when Aurora channel is not ready
  assign reset_c = RESET || !dly_data_xfer;

//______________________________ Transmit Data  __________________________________   
//Transmit data when TX_DST_RDY_N is asserted.
//Random data is generated using XNOR feedback LFSR
//TX_SRC_RDY_N is asserted on every cycle with data
always @(posedge USER_CLK)
	if(reset_c)
	begin
		data_lfsr_r 	<=  `DLY    16'hABCD;  //random seed value
		TX_SRC_RDY_N    <=  `DLY    1'b1;   
	end
	else if(!TX_DST_RDY_N)
	begin
		data_lfsr_r		<=  `DLY    {!{data_lfsr_r[3]^data_lfsr_r[12]^data_lfsr_r[14]^data_lfsr_r[15]},
							data_lfsr_r[0:14]};
		TX_SRC_RDY_N    <=  `DLY    1'b0;
	end

//Connect TX_D to the DATA LFSR register
assign  TX_D    =   {2{data_lfsr_r}};
   
endmodule

       

        代码不多,咱直接一段一段的拆:

2.3.1、端口

// User Interface
output  [0:31]     TX_D;
output             TX_SRC_RDY_N;
input              TX_DST_RDY_N;

// System Interface
input              USER_CLK;
input              RESET; 
input              CHANNEL_UP;

        

  •         TX_DST_RDY_N:等同于s_axi_tx_tready,置位时表示此时IP核准备好发送数据了
  •         TX_SRC_RDY_N:等同于s_axi_tx_tvalid,置位时表示此时用户准备通过IP核发送数据了
  •         TX_D:等同于s_axi_tx_data,欲发送的数据
  •         USER_CLK:用户时钟,用户对IP核的操作都需要在这个时钟下
  •         RESET:复位信号,由support模块生成,可能是GT的硬复位,也可能是传输错误导致的软复位
  •         CHANNEL_UP:置位表示此时对应的channel初始化完成,处于正常工作的状态

        再来看看图对比理解:

 2.3.2、等待初始化

  always @ (posedge USER_CLK)
  begin
    if(RESET)
        channel_up_cnt <= `DLY 5'd0;
    else if(CHANNEL_UP)
      if(&channel_up_cnt)
        channel_up_cnt <= `DLY channel_up_cnt;
      else 
        channel_up_cnt <= `DLY channel_up_cnt + 1'b1;
    else
      channel_up_cnt <= `DLY 5'd0;
  end

  assign dly_data_xfer = (&channel_up_cnt);

  //Generate RESET signal when Aurora channel is not ready
  assign reset_c = RESET || !dly_data_xfer;

        如果CHANNEL_UP还没有起来,那么channel_up_cnt将为全0,而dly_data_xfer为0,所以语句assign reset_c = RESET || !dly_data_xfer恒成立,reset_c恒为1,所以一直处于复位状态。

        当CHANNEL_UP起来后,channel_up_cnt需要一定的时间才能计数到5‘b11111,然后dly_data_xfer为1,所以此时assign reset_c = RESET || !dly_data_xfer等价于assign reset_c = RESET,即RESET控制reset_c信号。而RESET一般在CHANNEL_UP起来前就处于无效复位状态了,所以此时通道进入正常工作模式。

2.3.3、LFSR的使用

always @(posedge USER_CLK)
	if(reset_c)
	begin
		data_lfsr_r 	<=  `DLY    16'hABCD;  //random seed value
		TX_SRC_RDY_N    <=  `DLY    1'b1;   
	end
	else if(!TX_DST_RDY_N)
	begin
		data_lfsr_r		<=  `DLY    {!{data_lfsr_r[3]^data_lfsr_r[12]^data_lfsr_r[14]^data_lfsr_r[15]},
							data_lfsr_r[0:14]};
		TX_SRC_RDY_N    <=  `DLY    1'b0;
	end

//Connect TX_D to the DATA LFSR register
assign  TX_D    =   {2{data_lfsr_r}};

        关于LFSR可参考:FPGA实现的线性反馈移位寄存器LFSR

        此时载入的种子为16'hABCD。

2.4、数据检查模块(接受与检查)

        既然数据有发那就有收,同时既然是例程,那么我收了你的货也得验验货不是(检查收发过程的正确性)?

        check模块的完整代码如下:

module aurora_8b10b_0_FRAME_CHECK
(
    // User Interface
    RX_D, 
    RX_SRC_RDY_N,

    // System Interface
    USER_CLK,      
    RESET,
    CHANNEL_UP,
    ERR_COUNT
);

//***********************************Port Declarations*******************************

// User Interface
input   [0:31]     RX_D;
input              RX_SRC_RDY_N;   
// System Interface
input              USER_CLK;
input              RESET; 
input              CHANNEL_UP;

output  [0:7]      ERR_COUNT;
//***************************Internal Register Declarations***************************
// Slack registers
reg   [0:31]     RX_D_SLACK;
reg              RX_SRC_RDY_N_SLACK;
reg   [0:8]      err_count_r = 9'd0;
// RX Data registers
reg   [0:15]     data_lfsr_r;
   
//*********************************Wire Declarations**********************************
  
wire               reset_c;
wire    [0:31]     data_lfsr_concat_w;
wire               data_valid_c;   
wire               data_err_detected_c;
reg                data_err_detected_r;
   
//*********************************Main Body of Code**********************************

//Generate RESET signal when Aurora channel is not ready
assign reset_c = RESET;

// SLACK registers

always @ (posedge USER_CLK)
begin
  RX_D_SLACK          <= `DLY RX_D;
  RX_SRC_RDY_N_SLACK  <= `DLY RX_SRC_RDY_N;
end

    //______________________________ Capture incoming data ___________________________   
    //Data is valid when RX_SRC_RDY_N is asserted
    assign  data_valid_c    =   !RX_SRC_RDY_N_SLACK;
   

    //generate expected RX_D using LFSR
    always @(posedge USER_CLK)
        if(reset_c)
        begin
            data_lfsr_r          <=  `DLY    16'hD5E6;  //random seed value
        end
        else if(CHANNEL_UP)
        begin
          if(data_valid_c)
           data_lfsr_r          <=  `DLY    {!{data_lfsr_r[3]^data_lfsr_r[12]^data_lfsr_r[14]^data_lfsr_r[15]},
                                data_lfsr_r[0:14]};
        end
        else 
        begin
           data_lfsr_r          <=  `DLY    16'hD5E6;  //random seed value
        end 

    assign data_lfsr_concat_w = {2{data_lfsr_r}};

    //___________________________ Check incoming data for errors __________________________
        
   
    //An error is detected when LFSR generated RX data from the data_lfsr_concat_w register,
    //does not match valid data from the RX_D port
    assign  data_err_detected_c    = (data_valid_c && (RX_D_SLACK != data_lfsr_concat_w));

    //We register the data_err_detected_c signal for use with the error counter logic
    always @(posedge USER_CLK)
      data_err_detected_r    <=  `DLY    data_err_detected_c; 

    //Compare the incoming data with calculated expected data.
    //Increment the ERROR COUNTER if mismatch occurs.
    //Stop the ERROR COUNTER once it reaches its max value (i.e. 255)
    always @(posedge USER_CLK)
        if(CHANNEL_UP)
        begin
          if(&err_count_r)
            err_count_r       <=  `DLY    err_count_r;
          else if(data_err_detected_r)
            err_count_r       <=  `DLY    err_count_r + 1;
        end
	else
        begin	       	
          err_count_r       <=  `DLY    9'd0;
	end   

    //Here we connect the lower 8 bits of the count (the MSbit is used only to check when the counter reaches
    //max value) to the module output
    assign  ERR_COUNT =   err_count_r[1:8];

endmodule 

       

        代码继续拆分,走着!

2.4.1、端口与中间变量

// User Interface
input   [0:31]     RX_D;
input              RX_SRC_RDY_N;   
// System Interface
input              USER_CLK;
input              RESET; 
input              CHANNEL_UP;

output  [0:7]      ERR_COUNT;
//***************************Internal Register Declarations***************************
// Slack registers
reg   [0:31]     RX_D_SLACK;
reg              RX_SRC_RDY_N_SLACK;
reg   [0:8]      err_count_r = 9'd0;
// RX Data registers
reg   [0:15]     data_lfsr_r;
   
//*********************************Wire Declarations**********************************
  
wire               reset_c;
wire    [0:31]     data_lfsr_concat_w;
wire               data_valid_c;   
wire               data_err_detected_c;
reg                data_err_detected_r;
  •         RX_SRC_RDY_N:等同于m_axi_rx_tvalid,置位时表示总线上的数据为有效信号
  •         RX_D:等同于m_axi_rx_tdata,从总线上接收的数据
  •         USER_CLK:用户时钟,用户对IP核的操作都需要在这个时钟下
  •         RESET:复位信号,由support模块生成,可能是GT的硬复位,也可能是传输错误导致的软复位
  •         CHANNEL_UP:置位表示此时对应的channel初始化完成,处于正常工作的状态
  •         ERR_COUNT:对接收过程中的错误进行计数

       

        再来看看图对比理解:

2.4.2、端口寄存与LFSR数据生成

//Generate RESET signal when Aurora channel is not ready
assign reset_c = RESET;

// SLACK registers

always @ (posedge USER_CLK)
begin
  RX_D_SLACK          <= `DLY RX_D;
  RX_SRC_RDY_N_SLACK  <= `DLY RX_SRC_RDY_N;
end

//______________________________ Capture incoming data ___________________________   
//Data is valid when RX_SRC_RDY_N is asserted
assign  data_valid_c    =   !RX_SRC_RDY_N_SLACK;

//generate expected RX_D using LFSR
always @(posedge USER_CLK)
	if(reset_c)
		begin
			data_lfsr_r          <=  `DLY    16'hD5E6;  //random seed value
		end
	else if(CHANNEL_UP)
		begin
		  if(data_valid_c)
		   data_lfsr_r          <=  `DLY    {!{data_lfsr_r[3]^data_lfsr_r[12]^data_lfsr_r[14]^data_lfsr_r[15]},
								data_lfsr_r[0:14]};
		end
	else 
		begin
		   data_lfsr_r          <=  `DLY    16'hD5E6;  //random seed value
		end 

assign data_lfsr_concat_w = {2{data_lfsr_r}};

        assign reset_c = RESET。复位信号直接链接到RESET这没什么好说的。

always @ (posedge USER_CLK)
        begin
                RX_D_SLACK          <= `DLY RX_D;
                  RX_SRC_RDY_N_SLACK  <= `DLY RX_SRC_RDY_N;
        end

        这里将输入的数据及有效信号均寄存了一拍以便改善时许,实际上Xilinx大多数的例程中都是这样处理。

//Data is valid when RX_SRC_RDY_N is asserted
assign  data_valid_c    =   !RX_SRC_RDY_N_SLACK;

//generate expected RX_D using LFSR
always @(posedge USER_CLK)
    if(reset_c)
        begin
            data_lfsr_r          <=  `DLY    16'hD5E6;  //random seed value
        end
    else if(CHANNEL_UP)
        begin
          if(data_valid_c)
           data_lfsr_r          <=  `DLY    {!{data_lfsr_r[3]^data_lfsr_r[12]^data_lfsr_r[14]^data_lfsr_r[15]},
                                data_lfsr_r[0:14]};
        end
    else 
        begin
           data_lfsr_r          <=  `DLY    16'hD5E6;  //random seed value
        end 

assign data_lfsr_concat_w = {2{data_lfsr_r}};

        这一段是在通道正常起来后生成与发送模块对应的LFSR数据,以便后续比对来验证通讯过程是否正确。

2.4.3、数据正确性验证

//An error is detected when LFSR generated RX data from the data_lfsr_concat_w register,
//does not match valid data from the RX_D port
assign  data_err_detected_c    = (data_valid_c && (RX_D_SLACK != data_lfsr_concat_w));

//We register the data_err_detected_c signal for use with the error counter logic
always @(posedge USER_CLK)
  data_err_detected_r    <=  `DLY    data_err_detected_c; 

//Compare the incoming data with calculated expected data.
//Increment the ERROR COUNTER if mismatch occurs.
//Stop the ERROR COUNTER once it reaches its max value (i.e. 255)
always @(posedge USER_CLK)
	if(CHANNEL_UP)
	begin
	  if(&err_count_r)
		err_count_r       <=  `DLY    err_count_r;
	  else if(data_err_detected_r)
		err_count_r       <=  `DLY    err_count_r + 1;
	end
else
	begin	       	
	  err_count_r       <=  `DLY    9'd0;
end   

//Here we connect the lower 8 bits of the count (the MSbit is used only to check when the counter reaches
//max value) to the module output
assign  ERR_COUNT =   err_count_r[1:8];

endmodule 

        当接受到的收据与自身通过LFSR生成的数据不一致时,则拉高错误指示信号表示此时发生了传输错误,为了消除毛刺,还将此信号寄存了一拍。

        若错误指示信号为高,则将错误指示计数器+1。

3、仿真结果

        直接进行功能仿真直到自动停止,先看下整体框图:

        可以看到在50多us的时候,例化的两个模块的通道都已经建立了正常的连接(红框),然后一段时间后仿真结束。至于其他的话,各种时钟信号也都起来了,复位也都是正常的,所以仿真应该没啥问题。

        接下来我们看下第一个回环:第一个模块发送+第二个模块检查,只看下面两个模块(一发一收)的波形:

        信号太多,我们删去一些,只保留关键的时钟和发送的数据和检查的数据。

        发送端发送的数据:

        限于篇幅,只截取了前三个:d5e6d5e6----eaf3eaf3----f579f579。

        接收端接收到的数据:

        限于篇幅,只截取了前三个:d5e6d5e6----eaf3eaf3----f579f579。

        可以见到接受的数据与发送的数据一致,证明数据通道的第一个环回成功。

        第二个环回通道的配置--第二个模块发送+第一个模块检查,只看下面两个模块(一发一收)的波形:

         参考上面,直接看下发送端与接受端的数据:

         可以看到,情况与上述一致,那就证明第二个环回也成功了。

4、其他

4.1、一些问题

4.1.1、仿真速度

        vivado自带的simulator做仿真是真的挺慢的。而且Aurora这个IP核的仿真速度貌似还和选择的器件有关。一开始我直接按手里开发板的FPGA型号(A7)来生成官方例程,结果channel_up这个信号是一直拉不高,我等啊等,等啊等,差点给我人等崩溃了。

        最后发现仿真时间居然要1ms,如下:

        为了验证是否是器件仿真模型带来的影响,又分别选择一款K7器件和V7器件对IP核重新生成并仿真,其channel_up起来的时间分别如下。

        K7(50多us):

         V7(也是50多us):

        所以,如果您仿真时间过长,建议先换个器件试试(有可能仿真模型差距真就这么大)。

4.1.2、线速率与用户时钟的关系

        用户时钟是IP核反馈给我们的时钟,我们对数据的操作都应该在此时钟域下。

        用户时钟*数据位宽/0.8 = 线速率。除以0.8是因为8B/10B的编码方式下存在20%的编码开销,而lane的数量则与线速率无关,因为实际上一条lane就是一条线,也就是说线速率其实就是lane速率。

        将我们在上面测试的数据代入,线速率3.125Gbps,数据位宽32位,算得用户数据应该为:3.125Gbps * 0.8 / 32 = 78.125MHz,也就是时钟周期应该是1/78.125 = 12.8ns。

        我们在仿真界面测一下,用户时钟的周期,结果如下:12.8ns,正与理论一致。

 4.2、总结

  • 可以看到Aurora 8B/10B IP核 还是比较容易上手。在官方例程的基础上,把发送和接受模块稍微修改一下,差不多就可以直接拿过来进行简单的使用了。

  • 下一节我们再来一起学习下Aurora IP核的example design(Framing接口)。

  • 创作不易,如果本文对您有帮助,还请多多点赞、评论和收藏。您的支持是我持续更新的最大动力!

        

        

猜你喜欢

转载自blog.csdn.net/wuzhikaidetb/article/details/123814910