GEN模块分析web
CHECK模块分析微信
整体仿真并发
发送模块仿真dom
接收模块仿真编辑器
背景
博客首发地址:aurora布局
因为微信公众号的编辑器太难用,我已经差很少放弃在微信公众号上写东西了,这篇博客主要是测试mdnice这个编辑器怎么样,本文直接从CSDN的MD上复制的内容(有点卡)。学习
下面是原文:测试
熬夜继续写Aurora系列博文!今天明显状态不如昨天,眼皮有点涩涩的,坚持一下。若是不是写博客,我根本没有动力那么认真看,认真思考这个东西,这可能就是写博客输出的魅力吧。尽管streaming格式很简单,可是确定不如framing强大,不少项目仍是用framing用户接口,所以有必要对此进行仿真分析来认识下。flex
博客首页完成时间:次日晚凌晨4点左右。spa
定制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 Interfaceoutput [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 Interfaceinput 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 Interfaceoutput [0:15] TX_D; //dataoutput TX_REM; //?output TX_SOF_N; // start of frameoutput TX_EOF_N; // end of frameoutput TX_SRC_RDY_N; // validinput 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 machinereg 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 machinewire 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 machinewire 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_cframe_size_rbytes_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 Interfaceinput [0:15] RX_D;input RX_REM;input RX_SOF_N;input RX_EOF_N;input RX_SRC_RDY_N;
// System Interfaceinput 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则在非空闲状态下有效,空闲状态下无效。
若是处于空闲状态,则下一个时钟无效,若是不处于空闲状态,则下一个周期有效。总之,等价于状态延迟一个时钟。
接收模块仿真
有了上面的发送模块仿真的分析,我想接收模块的仿真也再也不话下了。
咱们就看看仿真结果就行了,至于结合程序分析,没有必要了,由于咱们接收完数据后,按照本身的方式处理了。这个本身最清楚。

接收真的比发送要简单多了。毕竟发送要设计状态机来组合要发送的数据。
参考资料
pg046
FPGA设计心得(3)Aurora IP core 的理论学习记录
FPGA设计心得(4)Aurora IP core 的定制详情记录
FPGA设计心得(5)Aurora 例子工程分析与仿真实例分析(streaming版)
高速串行总线系列(3)GTX/GTH 物理层结构分析
HDLBits 系列(21)LFSR(线性反馈移位寄存器)
HDLBits 系列(25)独热码有限状态机实现的简单方式
交个朋友
进不进无所谓,同行交个朋友!
写在最后
这篇博客,可真的花费了不少时间,熬夜两个夜晚,说实话,若是没有实际需求,我才不会花这个心思去搞这个东西。
一方面是实验室工程须要;另外一方面是之后工做也须要。
还有就是这个IP核的定制,虽然选择了framing帧格式,可是并无选择flow control,所以会不会有后续还说不许。
固然,还有数据手册的进一步细节学习,也说不许,所以后续应该会有的。
写的很差,还望见谅。
最后,写老师不催之恩(几天没催了,我也几天埋头学习,没有消息,你老是说我慢,我其实很用心!)。
2020/05/14
工程分享
连接:https://pan.baidu.com/s/1Tc2X6DWhOeO_AUiyE14UTQ 提取码:aw4x 或者csdn下载:https://download.csdn.net/download/Reborn_Lee/12417421
本文分享自微信公众号 - FPGA LAB(gh_af38c08c9983)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。