FPGA设计心得之Aurora IP核例子简析与仿真

可编程逻辑

1366人已加入

描述

定制framing接口的IP核

很简单,灵活配置如下参数即可:

状态机

本例选择小端模式。

FLow Control 暂时选择为None。(有必要后面专门研究,暂时最主要的还是弄懂用户接口信号的用法!)

状态机

为分析方面,选择单通道传输数据。

生成示例工程并分析

状态机

如图,右击IP核,打开例子程序,保存到一个位置,即可自动打开例子工程。

对于我们用户来说,最重要还是我们的用户程序,通过用户程序模块与Aurora IP核交互,生成数据发出以及接收IP核传输的数据。

状态机

文末同样会分享示例工程,所以这里就不把源码贴出来,占用篇幅,给阅读带来不便。

GEN模块分析

先打开gen模块,对于该模块,有一段描述:

//  Description: This module is a pattern generator to test the Aurora
//               designs in hardware. It generates data and passes it
//               through the Aurora channel. If connected to a framing
//               interface, it generates frames of varying size and
//               separation. LFSR is used to generate the pseudo-random
//               data and lower bits of LFSR are connected to REM bus.

翻译过来:

该模块是一个模式生成器,用于在硬件中测试Aurora设计。它生成数据并将其通过Aurora通道。如果连接到成帧接口,它将生成大小和间隔不同的帧。LFSR用于生成伪随机数据,并且LFSR的低位连接到REM总线。

首先,读了这段描述,一般肯定不知道具体干啥的,但是大概知道是生成一系列数据,并发送出去,而且用的是framing数据格式。

让我们看看具体内容:

看程序首先看输入输出:

// User Interface
output  [0:15]     TX_D;
output             TX_REM;
output             TX_SOF_N;
output             TX_EOF_N;
output             TX_SRC_RDY_N;
input              TX_DST_RDY_N;


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

从这几个信号用户接口,可见,有一些我们熟悉的接口,但是和axi接口的名字起的不一样罢了;

状态机

让我们对应下:

从如下接口方向可以断定:

状态机

s_axi_tx_tready为

input              TX_DST_RDY_N;

二者之间的关系:

状态机

作为gen模块用户逻辑的条件,无论是tready还是RDY_N,有效即可。

tready为RDY_N的反,从以N结尾也该明白了。

也不卖关子了,其他的等价:

// User Interface
output  [0:15]     TX_D; //data
output             TX_REM; //?
output             TX_SOF_N; // start of frame
output             TX_EOF_N; // end of frame
output             TX_SRC_RDY_N; // valid
input              TX_DST_RDY_N; //tready

当Aurora通道还未准备好时,需要设计复位,让通道出于复位状态:

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一直计数到5'b1111_1并保持;否则,channel_up_cnt为0;这样的话,当CHANNEL_UP无效时,dly_data_xfer为0,那么reset_c为1,即处于复位状态。

当CHANNEL_UP为1,也即有效时,也会计数一段时间,确保稳定,之后dly_data_xfer为1,那么reset_c的值取决于RESET,RESET无效时,停止复位。这样确保了,RESET早就停止了复位,而通道还未准备好,等通道准备好了之后,才停止复位,发送逻辑开始有效执行。

下面继续分析数据传输的部分:

//______________________________ Transmit Data  __________________________________   
    //Generate random data using XNOR feedback LFSR
    always @(posedge USER_CLK)
        if(reset_c)
        begin
            data_lfsr_r          <=  `DLY    16'hABCD;  //random seed value
        end
        else if(!TX_DST_RDY_N && !idle_r)
        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]};
        end



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

可见,要发送的数据是一个有规则产生的随机数据,data_lfsr_r的初始值作为随机数的种子,之后通过异或非的方式产生随机数。

这种产生随机数的方式属于线性反馈移位寄存器

上面出现了一个陌生的变量idle_r:

//State registers for one-hot state machine
reg                idle_r;
reg                single_cycle_frame_r;
reg                sof_r;
reg                data_cycle_r;
reg                eof_r;    

wire               reset_c;
//*********************************Wire Declarations**********************************

wire               ifg_done_c;

    //Next state signals for one-hot state machine
wire               next_idle_c;
wire               next_single_cycle_frame_c;
wire               next_sof_c;
wire               next_data_cycle_c;
wire               next_eof_c;

它是状态机变量,众多为了描述状态机而设的变量之一。下面便是状态机部分,可以看出,是一个三段式状态机,很讲究!

使用状态机的目的在于确定 frame的起始,结束以及要发送数据还是什么也不发送等。

//_____________________________ Framing State machine______________________________
    //Use a state machine to determine whether to start a frame, end a frame, send
    //data or send nothing

    //State registers for 1-hot state machine
    always @(posedge USER_CLK)
        if(reset_c)
        begin
            idle_r                  <=  `DLY    1'b1;
            single_cycle_frame_r    <=  `DLY    1'b0;
            sof_r                   <=  `DLY    1'b0;
            data_cycle_r            <=  `DLY    1'b0;
            eof_r                   <=  `DLY    1'b0;
        end
        else if(!TX_DST_RDY_N)
        begin
            idle_r                  <=  `DLY    next_idle_c;
            single_cycle_frame_r    <=  `DLY    next_single_cycle_frame_c;
            sof_r                   <=  `DLY    next_sof_c;
            data_cycle_r            <=  `DLY    next_data_cycle_c;
            eof_r                   <=  `DLY    next_eof_c;
        end


    //Nextstate logic for 1-hot state machine
    assign  next_idle_c                 =   !ifg_done_c &&
                                            (single_cycle_frame_r || eof_r || idle_r);

    assign  next_single_cycle_frame_c   =   (ifg_done_c && (frame_size_r == 0)) &&
                                            (idle_r || single_cycle_frame_r || eof_r);

    assign  next_sof_c                  =   (ifg_done_c && (frame_size_r != 0)) &&
                                            (idle_r || single_cycle_frame_r || eof_r);

    assign  next_data_cycle_c           =   (frame_size_r != bytes_sent_r) &&
                                            (sof_r || data_cycle_r);

    assign  next_eof_c                  =   (frame_size_r == bytes_sent_r) &&
                                            (sof_r || data_cycle_r);


    //Output logic for 1-hot state machine
    always @(posedge USER_CLK)
        if(reset_c)
        begin
            TX_SOF_N        <=  `DLY    1'b1;
            TX_EOF_N        <=  `DLY    1'b1;
            TX_SRC_RDY_N    <=  `DLY    1'b1;   
        end
        else if(!TX_DST_RDY_N)
        begin
            TX_SOF_N        <=  `DLY    !(sof_r || single_cycle_frame_r);
            TX_EOF_N        <=  `DLY    !(eof_r || single_cycle_frame_r);
            TX_SRC_RDY_N    <=  `DLY    idle_r;
        end

程序设计的是一个所谓的独热码状态机,且不是一般的独热码设计方法,类似于:hdlbits,独热码状态机设计,非常重要

这个状态机有5个状态,每循环一次,就可以发送一帧数据。

五个状态如下:

reg                idle_r; // 空闲状态
reg                single_cycle_frame_r; //单字帧,也就是一帧数据只有一个字或者少于一个字长
reg                sof_r; //帧起始
reg                data_cycle_r; //有效赋值数据
reg                eof_r;  //帧结束

由于是独热码,故都是一位变量;

次态变量:

//Next state signals for one-hot state machine
wire               next_idle_c;
wire               next_single_cycle_frame_c;
wire               next_sof_c;
wire               next_data_cycle_c;
wire               next_eof_c;

对应的次态部分代码:

//Nextstate logic for 1-hot state machine
    assign  next_idle_c                 =   !ifg_done_c &&
                                            (single_cycle_frame_r || eof_r || idle_r);

    assign  next_single_cycle_frame_c   =   (ifg_done_c && (frame_size_r == 0)) &&
                                            (idle_r || single_cycle_frame_r || eof_r);

    assign  next_sof_c                  =   (ifg_done_c && (frame_size_r != 0)) &&
                                            (idle_r || single_cycle_frame_r || eof_r);

    assign  next_data_cycle_c           =   (frame_size_r != bytes_sent_r) &&
                                            (sof_r || data_cycle_r);

    assign  next_eof_c                  =   (frame_size_r == bytes_sent_r) &&
                                            (sof_r || data_cycle_r);

可见,出现了很多条件来组成独热码:如:

ifg_done_c
frame_size_r
bytes_sent_r

要搞清楚这些输入的含义,才能更好理解,根据次态生成部分以及输出生成部分,还可以画出状态转移图或者状态转移表,这是后面的工作(看心情)。

状态机

都在这里 :

//Use a counter to determine the size of the next frame to send
    always @(posedge USER_CLK)
        if(reset_c)  
            frame_size_r    <=  `DLY    8'h00;
        else if(single_cycle_frame_r || eof_r)
            frame_size_r    <=  `DLY    frame_size_r + 1;

    //Use a second counter to determine how many bytes of the frame have already been sent
    always @(posedge USER_CLK)
        if(reset_c)
            bytes_sent_r    <=  `DLY    8'h00;
        else if(sof_r)
            bytes_sent_r    <=  `DLY    8'h01;
        else if(!TX_DST_RDY_N && !idle_r)
            bytes_sent_r    <=  `DLY    bytes_sent_r + 1;


    //Use a freerunning counter to determine the IFG
    always @(posedge USER_CLK)
        if(reset_c)
            ifg_size_r      <=  `DLY    4'h0;
        else
            ifg_size_r      <=  `DLY    ifg_size_r + 1;

    //IFG is done when ifg_size register is 0
    assign  ifg_done_c  =   (ifg_size_r == 4'h0);

frame_size_r是一个计数器变量,使用计数器确定要发送的一帧数据的大小;

同理,bytes_sent_r 使用第二个计数器来确定已经发送了多少个帧字节;

最难理解的属于ifg了?

这是什么玩意?

通过查阅资料恍然大悟:IFG是Interframe Gap的缩写,意思是帧与帧之间的间距。

为什么要有这个东西呢?简单地说,就是为了防止帧间距过小,而导致丢帧等,也就是说发送完一帧数据后,给下一帧数据的发送预留缓冲时间。

从程序中也能看出来:

第一部分:

//Use a freerunning counter to determine the IFG
    always @(posedge USER_CLK)
        if(reset_c)
            ifg_size_r      <=  `DLY    4'h0;
        else
            ifg_size_r      <=  `DLY    ifg_size_r + 1;

    //IFG is done when ifg_size register is 0
    assign  ifg_done_c  =   (ifg_size_r == 4'h0);

第二部分:

//Nextstate logic for 1-hot state machine
    assign  next_idle_c                 =   !ifg_done_c &&
                                            (single_cycle_frame_r || eof_r || idle_r);

    assign  next_single_cycle_frame_c   =   (ifg_done_c && (frame_size_r == 0)) &&
                                            (idle_r || single_cycle_frame_r || eof_r);

    assign  next_sof_c                  =   (ifg_done_c && (frame_size_r != 0)) &&
                                            (idle_r || single_cycle_frame_r || eof_r);

ifg_size_r为计数变量,一直计数,计数满了之后溢出,自身 变为零,继续计数,一直如此。

当ifg_size_r不为零的时候,状态机出于idle状态,也就是空闲状态,等溢出之后的下一个周期,就可以进入下一个状态了,发送数据。

说了这么多,其实状态机 描述的就是一个帧数据的发送过程:

状态机

idle_r状态不发送数据;

single_cycle_frame_r这个状态什么时候会进入呢?就是要发送的数据就一个字或者更少,就进入这个状态,因为framing协议要求,其实标志sof和结束标志eof都要有效(此时);

sof_r:如果要发送的数据多于一个字,那么安装情况就要分为几个周期完成数据发送;那么此时就会进入发送首字的状态;

data_cycle_r:这个状态,继续发送中间字;

eof_r:这就是要发送最后一个字了。

好了,我们的发送过程就讲完了。

CHECK模块分析

如果你知道了发送的过程,那收还不容易吗?

通信双方要按规矩办事,这个规矩就是协议!

首先看下输入输出定义,这也是看程序的第一步:

// User Interface
input   [0:15]     RX_D;
input              RX_REM;
input              RX_SOF_N;
input              RX_EOF_N;
input              RX_SRC_RDY_N;



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


output  [0:7]      ERR_COUNT;

通过发送的过程,这里我们也心照不宣地领会到,CHANNEL_UP和复位有关;

RX_D是要收的数据;

RX_SOF_N是首字;

RX_EOF_N是末字;

RX_SRC_RDY_N为有效信号,即Valid,它有效的时候我们才能采样到有效的数据。

如果SOF以及EOF同时有效,那么我们知道这个帧就一个字,或者更少。

继续看:

// SLACK registers


always @ (posedge USER_CLK)
begin
  RX_D_SLACK          <= `DLY RX_D;
  RX_SRC_RDY_N_SLACK  <= `DLY RX_SRC_RDY_N;
  RX_REM_1SLACK       <= `DLY RX_REM;
  RX_REM_2SLACK       <= `DLY RX_REM;
  RX_SOF_N_SLACK      <= `DLY RX_SOF_N;
  RX_EOF_N_SLACK      <= `DLY RX_EOF_N;
end

程序对输入的变量都寄存了一拍,为什么呢?很简单,还不是为了改善时序,这样让布局布线更加容易。

接着给出了一些标志信号:

assign  data_in_frame_c  =   data_in_frame_r  ||  (!RX_SRC_RDY_N_SLACK && !RX_SOF_N_SLACK);

其中有:

//Start a multicycle frame when a frame starts without ending on the same cycle. End
    //the frame when an EOF is detected
    always @(posedge USER_CLK)
        if(reset_c)  
            data_in_frame_r  <=  `DLY    1'b0;
        else if(CHANNEL_UP)
        begin
          if(!data_in_frame_r && !RX_SOF_N_SLACK && !RX_SRC_RDY_N_SLACK && RX_EOF_N_SLACK)
            data_in_frame_r  <=  `DLY    1'b1;
          else if(data_in_frame_r && !RX_SRC_RDY_N_SLACK && !RX_EOF_N_SLACK)
            data_in_frame_r  <=  `DLY    1'b0;
        end

先解释下data_in_frame_r:

有条件:!data_in_frame_r && !RX_SOF_N_SLACK && !RX_SRC_RDY_N_SLACK && RX_EOF_N_SLACK

可知,在帧开始后,为1;

又:data_in_frame_r && !RX_SRC_RDY_N_SLACK && !RX_EOF_N_SLACK 表示,在帧结束后,又变为0;

这点,在后面的行为仿真中,我们可以拉出来看看。

由这段分析可以知道data_in_frame_r在帧内(不包括sof有效的第一个周期)为1;那么:

data_in_frame_c呢?

assign data_in_frame_c = data_in_frame_r || (!RX_SRC_RDY_N_SLACK && !RX_SOF_N_SLACK);

表示如果数据是单周期帧或已启动多周期帧,则数据在该帧中。

它把帧的第一个周期也纳进去了。

怎么理解呢?

它等于data_in_frame_r与 !RX_SRC_RDY_N_SLACK && !RX_SOF_N_SLACK的或,也就是二者有其一就为1;

在帧的第一个周期内,!RX_SRC_RDY_N_SLACK && !RX_SOF_N_SLACK有效;在后面的周期内,二者均有效。这二者都有效了,肯定数据就在帧内了。那么这个信号data_in_frame_c就有效;

assign  data_valid_c    =   data_in_frame_c && !RX_SRC_RDY_N_SLACK;

这个就是把data_in_frame_c与!RX_SRC_RDY_N_SLACK进行一个与操作。作用于data_in_frame_c无异。

无论是单字帧(单周期帧)还是多周期帧,这个data_valid_c有效,数据一定是帧内有效数据。

//Register and decode the RX_D data with RX_REM bus
    always @ (posedge USER_CLK)
    begin 	      
      if((!RX_EOF_N_SLACK) && (!RX_SRC_RDY_N_SLACK))
      begin	
        case(RX_REM_1SLACK)
          1'd0 : RX_D_R <=  `DLY {RX_D_SLACK[0:7], 8'b0};
          1'd1 : RX_D_R <=  `DLY RX_D_SLACK;
          default : RX_D_R  <=  `DLY RX_D_SLACK; 		
	endcase 	
      end 
      else if(!RX_SRC_RDY_N_SLACK)
        RX_D_R          <=  `DLY    RX_D_SLACK;
    end

这段代码,包括后面的几乎都不用说了,就是把数据接收过来处理,换做你的工程,肯定按照自己的方式处理接收的数据。

那CHECK的分析到此结束吧。

示例工程仿真

仿真文件也就是例化两次例子程序,之后将二者的收发相接,形成一个环路。

总体仿真

这里直接仿真看我们想看的结果。

首先还是从宏观上看:

状态机

可以看出,1发2收,2发1收;

不过串行数据只能看到一个大概情况,更多 的细节,继续拉出来看:

状态机

状态机

状态机

可见,发的第一个数据和收的第一个数据一致!

后面的数据也是一致的。

发送模块仿真

从这里开始,我将关注gen模块的帧组成情况:

状态机

第一帧数据只有一个字,因此在发送的时候sof以及eof同时有效;第二帧:

状态机

第二帧数据有两个字:如上图,因此,第一个字sof有效,第二字eof有效。

我还想看看第一帧数据和第二帧数据之间的间隔是不是ifg_size_r 进行了计数:

状态机

确实在计数!

和代码对应起来:

//Use a freerunning counter to determine the IFG
    always @(posedge USER_CLK)
        if(reset_c)
            ifg_size_r      <=  `DLY    4'h0;
        else
            ifg_size_r      <=  `DLY    ifg_size_r + 1;

    //IFG is done when ifg_size register is 0
    assign  ifg_done_c  =   (ifg_size_r == 4'h0);

计数是一直进行的过程。

assign  next_idle_c                 =   !ifg_done_c &&
                                            (single_cycle_frame_r || eof_r || idle_r);

计数值不为0的时候,一直处于空闲状态。

assign  next_single_cycle_frame_c   =   (ifg_done_c && (frame_size_r == 0)) &&
                                            (idle_r || single_cycle_frame_r || eof_r);

    assign  next_sof_c                  =   (ifg_done_c && (frame_size_r != 0)) &&
                                            (idle_r || single_cycle_frame_r || eof_r);

计数值为0的时候,如果是单周期帧,则进入单周期帧状态,发送单周期数据。对于第一帧数据就是如此,直接进入单周期帧状态发送数据。将当前状态变量拉出来看看:

状态机

可见,一开始处于idle状态,之后进入单周期帧状态,在下一个周期便发送数据了。

assign  next_single_cycle_frame_c   =   (ifg_done_c && (frame_size_r == 0)) &&
                                            (idle_r || single_cycle_frame_r || eof_r);

由于进入单周期帧,需要另一个计数,就是帧长计数frame_size_r == 0;这个计数量的条件是:

//Use a counter to determine the size of the next frame to send
    always @(posedge USER_CLK)
        if(reset_c)  
            frame_size_r    <=  `DLY    8'h00;
        else if(single_cycle_frame_r || eof_r)
            frame_size_r    <=  `DLY    frame_size_r + 1;

可见,确实应该进入了单周期帧状态:

状态机

在分析下,下一帧不是单周期帧的情况:

状态机

在sof之后直接进入eof也很显而易见:

assign  next_eof_c                  =   (frame_size_r == bytes_sent_r) &&
                                            (sof_r || data_cycle_r);

满足了frame_size = bytes_size的条件。这两个计数器有什么关系呢?

//Use a counter to determine the size of the next frame to send
    always @(posedge USER_CLK)
        if(reset_c)  
            frame_size_r    <=  `DLY    8'h00;
        else if(single_cycle_frame_r || eof_r)
            frame_size_r    <=  `DLY    frame_size_r + 1;

    //Use a second counter to determine how many bytes of the frame have already been sent
    always @(posedge USER_CLK)
        if(reset_c)
            bytes_sent_r    <=  `DLY    8'h00;
        else if(sof_r)
            bytes_sent_r    <=  `DLY    8'h01;
        else if(!TX_DST_RDY_N && !idle_r)
            bytes_sent_r    <=  `DLY    bytes_sent_r + 1;

frame计数器呢?

如果发送单周期帧,则遇到单周期帧状态加1;

如果发送多周期帧,则遇到eof状态就加1;

可见,是不断加的。

而bytes呢?

是每次的帧开始就置1,然后一直加到eof状态;

assign  next_data_cycle_c           =   (frame_size_r != bytes_sent_r) &&
                                            (sof_r || data_cycle_r);

bytes计数的含义是已经发送的数据字数,如何和要发送的字数不符合,就处于next_data_cycle_c状态,这个状态是要一直发送数据的状态;

assign  next_eof_c                  =   (frame_size_r == bytes_sent_r) &&
                                            (sof_r || data_cycle_r);

如果等于了,则进入最后一个eof状态,发完最后一个字,结束。

//Output logic for 1-hot state machine
    always @(posedge USER_CLK)
        if(reset_c)
        begin
            TX_SOF_N        <=  `DLY    1'b1;
            TX_EOF_N        <=  `DLY    1'b1;
            TX_SRC_RDY_N    <=  `DLY    1'b1;   
        end
        else if(!TX_DST_RDY_N)
        begin
            TX_SOF_N        <=  `DLY    !(sof_r || single_cycle_frame_r);
            TX_EOF_N        <=  `DLY    !(eof_r || single_cycle_frame_r);
            TX_SRC_RDY_N    <=  `DLY    idle_r;
        end

有输出代码可知,输出都是在状态的基础上延迟一个时钟。

当sof_r状态的时候,下一个周期将TX_SOF_N置有效;

当eof_r状态的时候,下一个周期置TX_EOF_N有效;

而TX_SRC_RDY_N则在非空闲状态下有效,空闲状态下无效。

如果处于空闲状态,则下一个时钟无效,如果不处于空闲状态,则下一个周期有效。总之,等价于状态延迟一个时钟。

接收模块仿真

有了上面的发送模块仿真的分析,我想接收模块的仿真也不再话下了。

我们就看看仿真结果就好了,至于结合程序分析,没有必要了,因为我们接收完数据后,按照自己的方式处理了。这个自己最清楚。

状态机

接收真的比发送要简单多了。毕竟发送要设计状态机来组合要发送的数据。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分