定制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 registersalways @ (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则在非空闲状态下有效,空闲状态下无效。
如果处于空闲状态,则下一个时钟无效,如果不处于空闲状态,则下一个周期有效。总之,等价于状态延迟一个时钟。
接收模块仿真有了上面的发送模块仿真的分析,我想接收模块的仿真也不再话下了。
我们就看看仿真结果就好了,至于结合程序分析,没有必要了,因为我们接收完数据后,按照自己的方式处理了。这个自己最清楚。
接收真的比发送要简单多了。毕竟发送要设计状态机来组合要发送的数据。
射频识别技术漫谈(29)——射频接口芯片TRF7960
疫情催生远程办公热潮 网络安全威胁或将激增
继电维护设备的原理图
验证电阻网络的分压和分流特性实验方案
新处理器力撑ADAS 德州仪器发动智能驾驶攻势
FPGA设计心得之Aurora IP核例子简析与仿真
安徽电信完成5G﹢4G﹢C网全频段收编实验,打造4G/5G协同的极速网络
Vienna整流电路的基本原理和调制方式
PMSM电机热设计的关键领域包括哪些
SKM2105QR_B1I,B1C北斗三号定位模块+天线一体化IP67 GNSS G-mouse
角位变送器在水泥厂中的应用
STM32处理器存储空间布局结构说明
首款元宇宙手机原来是这样
VIAVI Solutions公司近日推出一系列产品和服务
如何选择适合的视频会议网络线路
BGA返修台取代热风枪,优势在哪里?
宝雅新能源15亿元获一汽吉林70.5%的股权 将顺利获得生产资质
手表镜片玻璃如何测量尺寸
LED神奇的面光源 光效更高更省电
轻量应用服务器到底有多好?华为云云耀云服务器 L 实例来告诉你