整理及仿真Xilinx的SRIO示例工程(高速收发器三十)
点击进入高速收发器系列文章导航界面
书接上N回,由于我对SRIO协议本身其实还不是特别了解,所以SRIO这部分一直没有继续写,这不CSDN官方的2024年度博客之星评选活动来了么。由于工作的几个项目都在最近回来了,根本没时间去写东西,不得不发以前的东西了。
2024年还是写了很多东西,2025年第一季度工作实在太忙,博客应该会从第二季度后持续更新。先打个广告,如果认为我的博客有帮助,最近这几天(2.14~2.20闭区间)可以帮我在CSDN的博客之星评选中投一下票,谢谢大家。
投票链接:https://www.csdn.net/blogstar2024/detail/225
同时谢谢已经为我投票的各位,下个季度开始会持续更新,最后可能会补齐一季度缺失的。
1、示例工程仿真
运行示例工程的仿真,结果如下所示,仿真大约需要140us才能跑完,需要耐心等待一会。
在收发数据之前,维护端口会先通过axi4_lite总线配置内部寄存器,之后才开始通信。
下图是复位时序,各个复位信号在各自时钟域下有效时间均大于4个时钟周期,符合前文的复位时序要求。
当port_initialized和link_initialized均拉高时,表示该IP复位初始化完成。如果在调试时这两个信号没有拉高,则应该查看时钟是否锁定,复位时序是否正常。
高速收发器0向高速收发器1发起第一个事务的时序如下所示。
上图中包头为64’h006020fc_d0000600,参考下图Hello数据包格式可知,该帧头表示时优先级为1,长度为16字节的流写操作时序,发送的16字节数据为64’h00000000_00000000和64’h01010101_01010101。
由于流写操作不需要应答,因此在应答通道的数据有效指示信号并不会拉高,如下图所示。
由于在仿真时,例化了两个示例工程模块短接进行仿真,因此在实际仿真时,两个高速收发器的时序是一致的,两个高速收发器会通过各自的ireq向对方发起事务请求,因此只需要关注一个高速收发器的时序即可。
因为上述原因,在ireq发起的事务请求,会出现在treq端口上,如果该请求需要响应,则会通过tresp发出,然后响应数据包出现在iresp上。
如下图所示,发起不需要响应的事务请求后会出现在接收事务请求通道上,如果发起需要响应的事务请求,则接收事务请求后,会返回响应信息,仿真的前一段时间发起的事务请求都是不需要响应的。
下图连续发送两个需要响应的请求事务时序,帧头数据为64’h25552050_04550002,后续发出的数据为64’hafafafaf_afafafaf。
通过下表可知上数据包是带响应的写请求事务,接收端需要应答该数据包。
在接收事务响应端口接收到上述数据包和数据,然后在响应通道发送一个无数据的响应数据包。
通过以上方式查看示例工程的其余仿真时序即可,不再赘述。
2、整理示例工程
与前文一样,为了便于后续扩展,应该将官方的示例工程进行整理,将共享逻辑放到顶层模块中,便于后续扩展高速收发器。
通道模块包含时钟生成模块、复位模块以及SRIO的IP,对应的RTL视图如下所示。
整理过程可以参考官方示例工程,整理后的参考代码如下所示:
assign user_clk = log_clk;//生成用户时钟信号;
assign user_rst = log_rst;//生成用户复位信号;
assign gt_qpll_rst = gt_pcs_rst;//根据示例工程,将PCS复位作为QPLL复位;
assign gt_qplllockdetclk = drpclk;
//例化时钟生成模块;
srio_gen2_0_srio_clk u_srio_gen2_0_srio_clk(
.gt_refclk ( gt_refclk ),//GT参考时钟信号;
.sys_rst ( sys_rst ),//系统复位信号;
.mode_1x ( mode_1x ),//表示链路已经训练到1个高速收发器通道。
.log_clk ( log_clk ),//逻辑部分时钟信号;
.phy_clk ( phy_clk ),//PHY时钟信号,是高速收发器参考时钟的四分之一。
.gt_pcs_clk ( gt_pcs_clk ),//高速收发器PCS时钟信号,是gt_clk的一半;
.gt_clk ( gt_clk ),//用于高速收发器的时钟;
.drpclk ( drpclk ),//配置时钟信号;
.clk_lock ( clk_lock ) //时钟锁定信号;
);
//例化复位生成模块;
srio_gen2_0_srio_rst u_srio_gen2_0_srio_rst(
.cfg_clk ( log_clk ),//配置端口时钟信号;
.log_clk ( log_clk ),//逻辑部分时钟信号;
.phy_clk ( phy_clk ),//PHY时钟信号,是高速收发器参考时钟的四分之一。
.gt_pcs_clk ( gt_pcs_clk ),//高速收发器PCS时钟信号,是gt_clk的一半;
.sys_rst ( sys_rst ),//系统复位信号;
.port_initialized ( port_initialized ),//表示PHY已经收到至少四个连续的、无错误的链路复位控制符号。
.phy_rcvd_link_reset ( phy_rcvd_link_reset ),//
.force_reinit ( 1'b0 ),//
.clk_lock ( clk_lock ),//时钟锁定信号;
.controlled_force_reinit ( force_reinit ),//该信号强制PHY重新初始化链路。
.cfg_rst ( cfg_rst ),//配置端口复位信号;
.log_rst ( log_rst ),//逻辑部分复位信号;
.buf_rst ( buf_rst ),//buf缓冲区复位信号;
.phy_rst ( phy_rst ),//PHY复位信号;
.gt_pcs_rst ( gt_pcs_rst ) //高速收发器PCS复位信号。
);
//例化SRIO IP;
srio_gen2_0 u_srio_gen2_0 (
.gt_clk_in ( gt_clk ),//用于高速收发器的时钟;
.drpclk_in ( drpclk ),//配置时钟信号;
.refclk_in ( gt_refclk ),//参考时钟信号;
.log_clk_in ( log_clk ),//逻辑部分时钟信号;
.log_rst_in ( log_rst ),//逻辑部分复位信号;
.phy_clk_in ( phy_clk ),//PHY时钟信号,是高速收发器参考时钟的四分之一。
.phy_rst_in ( phy_rst ),//PHY复位信号;
.gt_pcs_clk_in ( gt_pcs_clk ),//高速收发器PCS时钟信号,是gt_clk的一半;
.gt_pcs_rst_in ( gt_pcs_rst ),//高速收发器PCS复位信号。
.clk_lock_in ( clk_lock ),//时钟锁定信号;
.buf_rst_in ( buf_rst ),//buf缓冲区复位信号;
.cfg_rst_in ( cfg_rst ),//配置端口复位信号;
.deviceid ( deviceid ),//存储在基本设备ID CSR中的当前值(偏移量0x60)。
.port_decode_error ( port_decode_error ),//高电平表示收到了不支持的数据包,当接收到支持的数据包时拉低。
.s_axis_ireq_tvalid ( s_axis_ireq_tvalid ),//用户发送数据端口的数据请求的AXI数据有效指示信号;
.s_axis_ireq_tready ( s_axis_ireq_tready ),//用户发送数据端口的数据请求的AXI数据应答信号;
.s_axis_ireq_tlast ( s_axis_ireq_tlast ),//用户发送数据端口的数据请求的AXI最后一个数据有效指示信号;
.s_axis_ireq_tdata ( s_axis_ireq_tdata ),//用户发送数据端口的数据请求的AXI数据信号;
.s_axis_ireq_tkeep ( s_axis_ireq_tkeep ),//用户发送数据端口的数据请求的AXI数据掩码信号;
.s_axis_ireq_tuser ( s_axis_ireq_tuser ),//用户发送数据端口的数据请求的AXI的源ID和目的ID信号;
.m_axis_iresp_tvalid ( m_axis_iresp_tvalid ),//用户发送数据端口的数据响应的AXI数据有效指示信号;
.m_axis_iresp_tready ( m_axis_iresp_tready ),//用户发送数据端口的数据响应的AXI数据应答信号;
.m_axis_iresp_tlast ( m_axis_iresp_tlast ),//用户发送数据端口的数据响应的AXI最后一个数据有效指示信号;
.m_axis_iresp_tdata ( m_axis_iresp_tdata ),//用户发送数据端口的数据响应的AXI数据信号;
.m_axis_iresp_tkeep ( m_axis_iresp_tkeep ),//用户发送数据端口的数据响应的AXI数据掩码信号;
.m_axis_iresp_tuser ( m_axis_iresp_tuser ),//用户发送数据端口的数据响应的AXI的源ID和目的ID信号;
.m_axis_treq_tvalid ( m_axis_treq_tvalid ),//用户接收数据端口的数据请求的AXI数据有效指示信号;
.m_axis_treq_tready ( m_axis_treq_tready ),//用户接收数据端口的数据请求的AXI数据应答信号;
.m_axis_treq_tlast ( m_axis_treq_tlast ),//用户接收数据端口的数据请求的AXI最后一个数据有效指示信号;
.m_axis_treq_tdata ( m_axis_treq_tdata ),//用户接收数据端口的数据请求的AXI数据信号;
.m_axis_treq_tkeep ( m_axis_treq_tkeep ),//用户接收数据端口的数据请求的AXI数据掩码信号;
.m_axis_treq_tuser ( m_axis_treq_tuser ),//用户接收数据端口的数据请求的AXI的源ID和目的ID信号;
.s_axis_tresp_tvalid ( s_axis_tresp_tvalid ),//用户接收数据端口的数据响应的AXI数据有效指示信号;
.s_axis_tresp_tready ( s_axis_tresp_tready ),//用户接收数据端口的数据响应的AXI数据应答信号;
.s_axis_tresp_tlast ( s_axis_tresp_tlast ),//用户接收数据端口的数据响应的AXI最后一个数据有效指示信号;
.s_axis_tresp_tdata ( s_axis_tresp_tdata ),//用户接收数据端口的数据响应的AXI数据信号;
.s_axis_tresp_tkeep ( s_axis_tresp_tkeep ),//用户接收数据端口的数据响应的AXI数据掩码信号;
.s_axis_tresp_tuser ( s_axis_tresp_tuser ),//用户接收数据端口的数据响应的AXI的源ID和目的ID信号;
.s_axi_maintr_rst ( s_axi_maintr_rst ),//维护端口的axi_lite复位信号;
.s_axi_maintr_awvalid ( s_axi_maintr_awvalid ),//维护端口的axi_lite写地址有效指示信号;
.s_axi_maintr_awready ( s_axi_maintr_awready ),//维护端口的axi_lite写地址应答信号;
.s_axi_maintr_awaddr ( s_axi_maintr_awaddr ),//维护端口的axi_lite写地址信号;
.s_axi_maintr_wvalid ( s_axi_maintr_wvalid ),//维护端口的axi_lite写数据有效指示信号;
.s_axi_maintr_wready ( s_axi_maintr_wready ),//维护端口的axi_lite写数据应答信号;
.s_axi_maintr_wdata ( s_axi_maintr_wdata ),//维护端口的axi_lite写数据信号;
.s_axi_maintr_bvalid ( s_axi_maintr_bvalid ),//维护端口的axi_lite写数据应答指示信号;
.s_axi_maintr_bready ( s_axi_maintr_bready ),//维护端口的axi_lite写数据应答通道的应答信号;
.s_axi_maintr_bresp ( s_axi_maintr_bresp ),//维护端口的axi_lite写数据的应答状态信号;
.s_axi_maintr_arvalid ( s_axi_maintr_arvalid ),//维护端口的axi_lite读地址有效指示信号;
.s_axi_maintr_arready ( s_axi_maintr_arready ),//维护端口的axi_lite读地址应答信号;
.s_axi_maintr_araddr ( s_axi_maintr_araddr ),//维护端口的axi_lite读地址信号;
.s_axi_maintr_rvalid ( s_axi_maintr_rvalid ),//维护端口的axi_lite读数据有效指示信号;
.s_axi_maintr_rready ( s_axi_maintr_rready ),//维护端口的axi_lite读数据应答信号;
.s_axi_maintr_rdata ( s_axi_maintr_rdata ),//维护端口的axi_lite读数据信号;
.s_axi_maintr_rresp ( s_axi_maintr_rresp ),//维护端口的axi_lite读数据应答状态信号;
.buf_lcl_response_only_out ( ),//该信号表示发送缓冲器只能接受响应。
.buf_lcl_tx_flow_control_out ( ),//表示发送缓冲器处于发送器控制的流量控制模式。
.idle2_selected ( ),//拉高表示PHY在IDLE2模式下工作并使用长控制符号(因此ackIDs为6位)。
.idle_selected ( ),//高电平表示表示已选择IDLE1或IDLE2。
.buf_lcl_phy_buf_stat_out ( ),//用于表示接收缓冲器的状态。
.gt0_qpll_clk_in ( gt_qpll_clk ),//QPLL的时钟信号。
.gt0_qpll_out_refclk_in ( gt_qpll_out_refclk ),//QPLL的参考时钟信号。
.gt0_qpll_lock_in ( gt_qpll_lock ),//QPLL的时钟锁定信号。
.sim_train_en ( sim_train_en ),//高电平用于加快仿真速度。
.phy_mce ( 1'b0 ),//用于指示PHY发送MCE控制信号的单周期输入信号。
.phy_link_reset ( 1'b0 ),//只要该信号被拉高,PHY就发送链路复位控制符号。
.force_reinit ( force_reinit ),//该信号强制PHY重新初始化链路。
.phy_lcl_phy_next_fm_out ( ),//用于传送由ackID索引的下一个要发送的数据包。
.phy_lcl_phy_last_ack_out ( ),//用于传送接收到的最后一个应答控制符号的ackID。
.phy_lcl_phy_rewind_out ( ),//高电平表示PHY正在请求倒带。
.link_initialized ( ),//高电平表示链接已初始化。
.phy_lcl_phy_rcvd_buf_stat_out ( ),//用于传送从对方接收的状态控制符号中最后接收的缓冲状态。
.phy_rcvd_mce ( ),//表示PHY已从对方接收到MCE控制符号。
.phy_rcvd_link_reset ( phy_rcvd_link_reset ),//表示PHY已经收到至少四个连续的、无错误的链路复位控制符号。
.port_error ( port_error ),//表示端口收到不可恢复的错误,并处于错误状态。
.port_initialized ( port_initialized ),//表示端口已经初始化。
.mode_1x ( mode_1x ),//表示链路已经训练到1个高速收发器通道。
.port_timeout ( ),//反映端口链路超时控制CSR中超时值字段的值。
.srio_host ( ),//反映物理层配置中端口通用控制CSR的主机位的值。
.phy_lcl_master_enable_out ( ),//高电平表示BUF可以向PHY转发请求,低电平表示BUF只能转发响应。
.phy_lcl_maint_only_out ( ),//高电平表示逻辑层应该只传输维护事务。
.gtrx_disperr_or ( gtrx_disperr ),//高电平表示字节对齐错误。
.gtrx_notintable_or ( gtrx_notintable ),//高电平表示8B/10B解码错误。
.phy_debug ( ),//低205位表示各种对应的错误,可用于调试。
.srio_txn0 ( srio_tx_n ),//SRIO的发送数据差分信号;
.srio_txp0 ( srio_tx_p ),//SRIO的发送数据差分信号;
.srio_rxn0 ( srio_rx_n ),//SRIO的接收数据差分信号;
.srio_rxp0 ( srio_rx_p ) //SRIO的接收数据差分信号;
);
在上层调用两个通道模块,之后外部使用光纤回环测试IP通信,虽然该工程并不会使用QPLL,但是必须例化GT_COMMON,这是GTX手册中规定的,只要使用高速收发器,就必须例化QPLL。
该模块的参考代码如下所示:
//例化差分时钟转单端模块;
IBUFDS_GTE2 u_IBUFDS_GTE2(
.O ( gt_refclk ),//GT单端参考时钟信号;
.I ( gt_refclk_p ),//GT差分时钟信号;
.IB ( gt_refclk_n ),//GT差分时钟信号;
.CEB ( 1'b0 ),//差分转单端使能信号;
.ODIV2 ( ) //GT单端参考时钟二分频信号;
);
//例化QPLL模块;
srio_gen2_0_k7_v7_gtxe2_common u_srio_gen2_0_k7_v7_gtxe2_common(
.gt0_gtrefclk0_common_in( gt_refclk ),//GT单端参考时钟信号;
.gt0_qplllockdetclk_in ( gt_qplllockdetclk ),//
.gt0_qpllreset_in ( gt_qpll_rst ),//
.qpll_clk_out ( gt_qpll_clk ),//
.qpll_out_refclk_out ( gt_qpll_out_refclk ),//
.gt0_qpll_lock_out ( gt_qpll_lock ) //
);
//例化通道0的SRIO IP
srio_channel u_srio_channel_0(
.gt_refclk ( gt_refclk ),//参考时钟信号;
.sys_rst ( sys_rst ),//系统复位信号;
.user_clk ( user_clk_0 ),//生成用户时钟信号;
.user_rst ( user_rst_0 ),//生成用户复位信号;
.deviceid ( ),//存储在基本设备ID CSR中的当前值(偏移量0x60)。
.port_decode_error ( ),//高电平表示收到了不支持的数据包,当接收到支持的数据包时拉低
.s_axis_ireq_tvalid ( s0_axis_ireq_tvalid ),//用户发送数据端口的数据请求的AXI数据有效指示信号;
.s_axis_ireq_tready ( s0_axis_ireq_tready ),//用户发送数据端口的数据请求的AXI数据应答信号;
.s_axis_ireq_tlast ( s0_axis_ireq_tlast ),//用户发送数据端口的数据请求的AXI最后一个数据有效指示信号;
.s_axis_ireq_tdata ( s0_axis_ireq_tdata ),//用户发送数据端口的数据请求的AXI数据信号;
.s_axis_ireq_tkeep ( s0_axis_ireq_tkeep ),//用户发送数据端口的数据请求的AXI数据掩码信号;
.s_axis_ireq_tuser ( s0_axis_ireq_tuser ),//用户发送数据端口的数据请求的AXI的源ID和目的ID信号;
.m_axis_iresp_tvalid ( m0_axis_iresp_tvalid ),//用户发送数据端口的数据响应的AXI数据有效指示信号;
.m_axis_iresp_tready ( m0_axis_iresp_tready ),//用户发送数据端口的数据响应的AXI数据应答信号;
.m_axis_iresp_tlast ( m0_axis_iresp_tlast ),//用户发送数据端口的数据响应的AXI最后一个数据有效指示信号;
.m_axis_iresp_tdata ( m0_axis_iresp_tdata ),//用户发送数据端口的数据响应的AXI数据信号;
.m_axis_iresp_tkeep ( m0_axis_iresp_tkeep ),//用户发送数据端口的数据响应的AXI数据掩码信号;
.m_axis_iresp_tuser ( m0_axis_iresp_tuser ),//用户发送数据端口的数据响应的AXI的源ID和目的ID信号;
.m_axis_treq_tvalid ( m0_axis_treq_tvalid ),//用户接收数据端口的数据请求的AXI数据有效指示信号;
.m_axis_treq_tready ( m0_axis_treq_tready ),//用户接收数据端口的数据请求的AXI数据应答信号;
.m_axis_treq_tlast ( m0_axis_treq_tlast ),//用户接收数据端口的数据请求的AXI最后一个数据有效指示信号;
.m_axis_treq_tdata ( m0_axis_treq_tdata ),//用户接收数据端口的数据请求的AXI数据信号;
.m_axis_treq_tkeep ( m0_axis_treq_tkeep ),//用户接收数据端口的数据请求的AXI数据掩码信号;
.m_axis_treq_tuser ( m0_axis_treq_tuser ),//用户接收数据端口的数据请求的AXI的源ID和目的ID信号;
.s_axis_tresp_tvalid ( s0_axis_tresp_tvalid ),//用户接收数据端口的数据响应的AXI数据有效指示信号;
.s_axis_tresp_tready ( s0_axis_tresp_tready ),//用户接收数据端口的数据响应的AXI数据应答信号;
.s_axis_tresp_tlast ( s0_axis_tresp_tlast ),//用户接收数据端口的数据响应的AXI最后一个数据有效指示信号;
.s_axis_tresp_tdata ( s0_axis_tresp_tdata ),//用户接收数据端口的数据响应的AXI数据信号;
.s_axis_tresp_tkeep ( s0_axis_tresp_tkeep ),//用户接收数据端口的数据响应的AXI数据掩码信号;
.s_axis_tresp_tuser ( s0_axis_tresp_tuser ),//用户接收数据端口的数据响应的AXI的源ID和目的ID信号;
.s_axi_maintr_rst ( 1'b0 ),//维护端口的axi_lite复位信号;
.s_axi_maintr_awvalid ( 1'b0 ),//维护端口的axi_lite写地址有效指示信号;
.s_axi_maintr_awready ( ),//维护端口的axi_lite写地址应答信号;
.s_axi_maintr_awaddr ( 'd0 ),//维护端口的axi_lite写地址信号;
.s_axi_maintr_wvalid ( 1'b0 ),//维护端口的axi_lite写数据有效指示信号;
.s_axi_maintr_wready ( ),//维护端口的axi_lite写数据应答信号;
.s_axi_maintr_wdata ( 'd0 ),//维护端口的axi_lite写数据信号;
.s_axi_maintr_bvalid ( ),//维护端口的axi_lite写数据应答指示信号;
.s_axi_maintr_bready ( 1'b0 ),//维护端口的axi_lite写数据应答通道的应答信号;
.s_axi_maintr_bresp ( ),//维护端口的axi_lite写数据的应答状态信号;
.s_axi_maintr_arvalid ( 1'b0 ),//维护端口的axi_lite读地址有效指示信号;
.s_axi_maintr_arready ( ),//维护端口的axi_lite读地址应答信号;
.s_axi_maintr_araddr ( 'd0 ),//维护端口的axi_lite读地址信号;
.s_axi_maintr_rvalid ( ),//维护端口的axi_lite读数据有效指示信号;
.s_axi_maintr_rready ( 1'b0 ),//维护端口的axi_lite读数据应答信号;
.s_axi_maintr_rdata ( ),//维护端口的axi_lite读数据信号;
.s_axi_maintr_rresp ( ),//维护端口的axi_lite读数据应答状态信号;
.gt_qpll_clk ( gt_qpll_clk ),//QPLL的时钟信号。
.gt_qpll_out_refclk ( gt_qpll_out_refclk ),//QPLL的参考时钟信号。
.gt_qpll_lock ( gt_qpll_lock ),//QPLL的时钟锁定信号。
.gt_qpll_rst ( gt_qpll_rst ),//QPLL复位信号;
.gt_qplllockdetclk ( gt_qplllockdetclk ),
.sim_train_en ( TRAIN_EN ),//高电平用于加快仿真速度。
.port_error ( ),//表示端口收到不可恢复的错误,并处于错误状态。
.port_initialized ( port_initialized_0 ),//表示端口已经初始化。
.gtrx_disperr ( ),//高电平表示字节对齐错误。
.gtrx_notintable ( ),//高电平表示8B/10B解码错误。
.srio_tx_n ( srio_tx_0_n ),//SRIO的发送数据差分信号;
.srio_tx_p ( srio_tx_0_p ),//SRIO的发送数据差分信号;
.srio_rx_n ( srio_rx_0_n ),//SRIO的接收数据差分信号;
.srio_rx_p ( srio_rx_0_p ) //SRIO的接收数据差分信号;
);
//例化通道1的SRIO IP
srio_channel u_srio_channel_1(
.gt_refclk ( gt_refclk ),//参考时钟信号;
.sys_rst ( sys_rst ),//系统复位信号;
.user_clk ( user_clk_1 ),//生成用户时钟信号;
.user_rst ( user_rst_1 ),//生成用户复位信号;
.deviceid ( ),//存储在基本设备ID CSR中的当前值(偏移量0x60)。
.port_decode_error ( ),//高电平表示收到了不支持的数据包,当接收到支持的数据包时拉低
.s_axis_ireq_tvalid ( s1_axis_ireq_tvalid ),//用户发送数据端口的数据请求的AXI数据有效指示信号;
.s_axis_ireq_tready ( s1_axis_ireq_tready ),//用户发送数据端口的数据请求的AXI数据应答信号;
.s_axis_ireq_tlast ( s1_axis_ireq_tlast ),//用户发送数据端口的数据请求的AXI最后一个数据有效指示信号;
.s_axis_ireq_tdata ( s1_axis_ireq_tdata ),//用户发送数据端口的数据请求的AXI数据信号;
.s_axis_ireq_tkeep ( s1_axis_ireq_tkeep ),//用户发送数据端口的数据请求的AXI数据掩码信号;
.s_axis_ireq_tuser ( s1_axis_ireq_tuser ),//用户发送数据端口的数据请求的AXI的源ID和目的ID信号;
.m_axis_iresp_tvalid ( m1_axis_iresp_tvalid ),//用户发送数据端口的数据响应的AXI数据有效指示信号;
.m_axis_iresp_tready ( m1_axis_iresp_tready ),//用户发送数据端口的数据响应的AXI数据应答信号;
.m_axis_iresp_tlast ( m1_axis_iresp_tlast ),//用户发送数据端口的数据响应的AXI最后一个数据有效指示信号;
.m_axis_iresp_tdata ( m1_axis_iresp_tdata ),//用户发送数据端口的数据响应的AXI数据信号;
.m_axis_iresp_tkeep ( m1_axis_iresp_tkeep ),//用户发送数据端口的数据响应的AXI数据掩码信号;
.m_axis_iresp_tuser ( m1_axis_iresp_tuser ),//用户发送数据端口的数据响应的AXI的源ID和目的ID信号;
.m_axis_treq_tvalid ( m1_axis_treq_tvalid ),//用户接收数据端口的数据请求的AXI数据有效指示信号;
.m_axis_treq_tready ( m1_axis_treq_tready ),//用户接收数据端口的数据请求的AXI数据应答信号;
.m_axis_treq_tlast ( m1_axis_treq_tlast ),//用户接收数据端口的数据请求的AXI最后一个数据有效指示信号;
.m_axis_treq_tdata ( m1_axis_treq_tdata ),//用户接收数据端口的数据请求的AXI数据信号;
.m_axis_treq_tkeep ( m1_axis_treq_tkeep ),//用户接收数据端口的数据请求的AXI数据掩码信号;
.m_axis_treq_tuser ( m1_axis_treq_tuser ),//用户接收数据端口的数据请求的AXI的源ID和目的ID信号;
.s_axis_tresp_tvalid ( s1_axis_tresp_tvalid ),//用户接收数据端口的数据响应的AXI数据有效指示信号;
.s_axis_tresp_tready ( s1_axis_tresp_tready ),//用户接收数据端口的数据响应的AXI数据应答信号;
.s_axis_tresp_tlast ( s1_axis_tresp_tlast ),//用户接收数据端口的数据响应的AXI最后一个数据有效指示信号;
.s_axis_tresp_tdata ( s1_axis_tresp_tdata ),//用户接收数据端口的数据响应的AXI数据信号;
.s_axis_tresp_tkeep ( s1_axis_tresp_tkeep ),//用户接收数据端口的数据响应的AXI数据掩码信号;
.s_axis_tresp_tuser ( s1_axis_tresp_tuser ),//用户接收数据端口的数据响应的AXI的源ID和目的ID信号;
.s_axi_maintr_rst ( 1'b0 ),//维护端口的axi_lite复位信号;
.s_axi_maintr_awvalid ( 1'b0 ),//维护端口的axi_lite写地址有效指示信号;
.s_axi_maintr_awready ( ),//维护端口的axi_lite写地址应答信号;
.s_axi_maintr_awaddr ( 'd0 ),//维护端口的axi_lite写地址信号;
.s_axi_maintr_wvalid ( 1'b0 ),//维护端口的axi_lite写数据有效指示信号;
.s_axi_maintr_wready ( ),//维护端口的axi_lite写数据应答信号;
.s_axi_maintr_wdata ( 'd0 ),//维护端口的axi_lite写数据信号;
.s_axi_maintr_bvalid ( ),//维护端口的axi_lite写数据应答指示信号;
.s_axi_maintr_bready ( 1'b0 ),//维护端口的axi_lite写数据应答通道的应答信号;
.s_axi_maintr_bresp ( ),//维护端口的axi_lite写数据的应答状态信号;
.s_axi_maintr_arvalid ( 1'b0 ),//维护端口的axi_lite读地址有效指示信号;
.s_axi_maintr_arready ( ),//维护端口的axi_lite读地址应答信号;
.s_axi_maintr_araddr ( 'd0 ),//维护端口的axi_lite读地址信号;
.s_axi_maintr_rvalid ( ),//维护端口的axi_lite读数据有效指示信号;
.s_axi_maintr_rready ( 1'b0 ),//维护端口的axi_lite读数据应答信号;
.s_axi_maintr_rdata ( ),//维护端口的axi_lite读数据信号;
.s_axi_maintr_rresp ( ),//维护端口的axi_lite读数据应答状态信号;
.gt_qpll_clk ( gt_qpll_clk ),//QPLL的时钟信号。
.gt_qpll_out_refclk ( gt_qpll_out_refclk ),//QPLL的参考时钟信号。
.gt_qpll_lock ( gt_qpll_lock ),//QPLL的时钟锁定信号。
.gt_qpll_rst ( ),//QPLL复位信号;
.gt_qplllockdetclk ( ),
.sim_train_en ( TRAIN_EN ),//高电平用于加快仿真速度。
.port_error ( ),//表示端口收到不可恢复的错误,并处于错误状态。
.port_initialized ( port_initialized_1 ),//表示端口已经初始化。
.gtrx_disperr ( ),//高电平表示字节对齐错误。
.gtrx_notintable ( ),//高电平表示8B/10B解码错误。
.srio_tx_n ( srio_tx_1_n ),//SRIO的发送数据差分信号;
.srio_tx_p ( srio_tx_1_p ),//SRIO的发送数据差分信号;
.srio_rx_n ( srio_rx_1_n ),//SRIO的接收数据差分信号;
.srio_rx_p ( srio_rx_1_p ) //SRIO的接收数据差分信号;
);
3、测试数据生成模块
该模块用于生成SRIO IP的测试数据,主要用于产生ireq通道的数据,并且对来自treq通道的读请求做出响应。Ireq主要产生写数据、门铃、读数据、消息等4四个事务。
下面是控制ireq通道产生上述几个事务状态机的状态转换图。首先处于空闲状态一段时间后,通过ireq通道写入一帧数据,然后跳转到发送门铃消息状态,之后读取一帧数据,最后发送一个消息包,之后循环跳转。
该模块的端口信号列表如下所示,包含ireq、iresp、treq、tresp等四个通道。
module srio_gen_data #(
parameter DATA_LEN = 5'd16 //读写一帧数据的长度,单位8字节;
)(
input clk ,//系统时钟信号;
input rst ,//系统复位信号,高电平有效;
input m_axis_ireq_tready ,//
output reg m_axis_ireq_tvalid ,//
output reg m_axis_ireq_tlast ,//
output reg [63 : 0] m_axis_ireq_tdata ,//
output reg [7 : 0] m_axis_ireq_tkeep ,//
output reg [31 : 0] m_axis_ireq_tuser ,//
input s_axis_iresp_tvalid ,//
input s_axis_iresp_tlast ,//
input [63 : 0] s_axis_iresp_tdata ,//
input [7 : 0] s_axis_iresp_tkeep ,//
input [31 : 0] s_axis_iresp_tuser ,//
output reg s_axis_iresp_tready ,//
input s_axis_treq_tvalid ,//
input s_axis_treq_tlast ,//
input [63 : 0] s_axis_treq_tdata ,//
input [7 : 0] s_axis_treq_tkeep ,//
input [31 : 0] s_axis_treq_tuser ,//
output reg s_axis_treq_tready ,//
input m_axis_tresp_tready ,//
output reg m_axis_tresp_tvalid ,//
output reg m_axis_tresp_tlast ,//
output reg [63 : 0] m_axis_tresp_tdata ,//
output reg [7 : 0] m_axis_tresp_tkeep ,//
output reg [31 : 0] m_axis_tresp_tuser //
);
下面是状态机相关代码,包含次态与现态的跳转,以及次态的跳转,注意读数据时,需要等待数据返回之后才会跳转。
//状态机的次态与现态的转换;
always@(posedge clk)begin
if(rst)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//状态机次态的跳转;
always@(*)begin
case(state_c)
IDLE : begin
if(&st_cnt)begin//经过一段时间后发送一帧写数据;
state_n = WRTIE;
end
else begin
state_n = state_c;
end
end
WRTIE : begin//当写完一帧数据时,跳转到发送门铃消息的状态;
if(m_axis_ireq_tready && m_axis_ireq_tvalid && m_axis_ireq_tlast)begin
state_n = DB;
end
else begin
state_n = state_c;
end
end
DB : begin//当发送完一帧门铃消息时,跳转到读数据的状态;
if(m_axis_ireq_tready && m_axis_ireq_tvalid && m_axis_ireq_tlast)begin
state_n = READ;
end
else begin
state_n = state_c;
end
end
READ : begin//当读完一帧数据时,跳转到发送消息的状态;
if(s_axis_iresp_tready && s_axis_iresp_tvalid && s_axis_iresp_tlast)begin
state_n = MESSAGE;
end
else begin
state_n = state_c;
end
end
MESSAGE : begin//当发送完一帧消息后,跳转到空闲状态;
if(m_axis_ireq_tready && m_axis_ireq_tvalid && m_axis_ireq_tlast)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default : begin
state_n = IDLE;
end
endcase
end
下面是一个状态计数器,对状态机处于各个状态西发送或者接收数据的时钟进行计数,状态机跳转时清零。
always@(posedge clk)begin
if(rst)begin//初始值为0;
st_cnt <= 6'd0;
end//状态跳转时计数器清零;
else if(state_c != state_n)begin
st_cnt <= 6'd0;
end//当数据发送完或者处于空闲状态时加1.
else if((m_axis_ireq_tready && m_axis_ireq_tvalid) || (s_axis_iresp_tready && s_axis_iresp_tvalid) || (state_c == IDLE))begin
st_cnt <= st_cnt + 6'd1;
end
end
由于每次发送的数据都是8字节的整数倍,因此数据掩码信号全部设为无效即可,从机接口的应答信号一直拉高,处于接收数据状态。
//两个通道的应答信号一直有效;
always@(posedge clk)begin
s_axis_treq_tready <= 1'b1;
s_axis_iresp_tready <= 1'b1;
m_axis_ireq_tkeep <= 8'hff;//数据掩码信号;
m_axis_ireq_tuser <= 32'd0;//发送数据帧的目的ID和源ID;
m_axis_tresp_tuser <= 32'd0;//发送数据帧的目的ID和源ID;
m_axis_tresp_tkeep <= 8'hff;//数据掩码信号;
state_c_r <= state_c;
end
下面是ireq通道的数据有效指示信号,初始为低电平,当状态机跳转到另一个状态时拉高,当发送完一帧数据时拉低。
//生成数据有效指示信号;
always@(posedge clk)begin
if(rst)begin//初始值为0;
m_axis_ireq_tvalid <= 1'b0;
end//当发送完一帧数据时拉低;
else if(m_axis_ireq_tvalid && m_axis_ireq_tready && m_axis_ireq_tlast)begin
m_axis_ireq_tvalid <= 1'b0;
end
else if(st_cnt == 0)begin//当状态机需要写数据、发送门铃、读数据、发送消息时拉高。
m_axis_ireq_tvalid <= ((state_c == WRTIE) || (state_c == DB) || (state_c == READ) || (state_c == MESSAGE));
end
end
Ireq通道发送最后一个数据的有效指示信号,当发送最后一个数据时拉高,其余时间拉低。
always@(posedge clk)begin
if(rst)begin//初始值为0;
m_axis_ireq_tlast <= 1'b0;
end//当发送完一帧数据时拉低;
else if(m_axis_ireq_tvalid && m_axis_ireq_tready && m_axis_ireq_tlast)begin
m_axis_ireq_tlast <= 1'b0;
end
else if((state_c == WRTIE) && (st_cnt == DATA_LEN - 1))begin
m_axis_ireq_tlast <= 1'b1;
end
else if((state_c == DB || state_c == READ) && (st_cnt == 0))begin
m_axis_ireq_tlast <= 1'b1;
end
else if((state_c == MESSAGE) && (st_cnt == 7))begin
m_axis_ireq_tlast <= 1'b1;
end
end
生成ireq通道的数据,状态机跳转时生成帧头数据,其余时间生成连续的数据。
//生成发送数据;
always@(posedge clk)begin
if(rst)begin//初始值为0;
m_axis_ireq_tdata <= 'd0;
end
else if(state_c != state_c_r)begin
case (state_c)//发送帧头数据;
WRTIE : m_axis_ireq_tdata <= {8'd0,4'b0101,4'b0100,1'b0,2'b1,1'b0,{{DATA_LEN[4:0],3'd0} - 8'd1},2'b0,34'd0};
DB : m_axis_ireq_tdata <= {8'd0,4'b1010,4'd0,1'b0,2'b0,1'b0,8'd0,2'b0,34'd0};
READ : m_axis_ireq_tdata <= {8'd0,4'b0010,4'd4,1'b0,2'b0,1'b0,{{DATA_LEN[4:0],3'd0} - 8'd1},2'b0,34'd0};
MESSAGE:m_axis_ireq_tdata <= {4'd0,4'd0,4'b1011,4'd0,1'b0,2'b0,1'b0,8'd63,2'b0,34'd0};
default : ;
endcase
end//其余时间将计数器数据拼接作为测试数据;
else if(m_axis_ireq_tvalid && m_axis_ireq_tready)begin
m_axis_ireq_tdata <= {{9'd0,st_cnt[4:0],2'd0},{9'd0,st_cnt[4:0],2'd1},{9'd0,st_cnt[4:0],2'd2},{9'd0,st_cnt[4:0],2'd3}};
end
end
下面是响应读命令的相关代码,rx_flag为高电平时,表示接收一帧treq数据,然后检测treq的读请求指令,并且解析出需要读取数据的长度,单位8个字节。
//检测接收的读命令;
always@(posedge clk)begin
if(rst)begin//初始值为0;
rx_flag <= 1'b0;
end
else if(s_axis_treq_tvalid && s_axis_treq_tready)begin
if(s_axis_treq_tlast)//检测到一帧数据的最后一个数据时拉低;
rx_flag <= 1'b0;
else
rx_flag <= 1'b1;
end
end
//检测到读指令。
always@(posedge clk)begin
if(rst)begin//初始值为0;
read_cmd <= 1'b0;
read_data_len <= 'd31;
end//当接收到读指令时将读命令拉高;
else if(s_axis_treq_tvalid && s_axis_treq_tready && (~rx_flag))begin
read_cmd <= (s_axis_treq_tdata[55:48] == 8'h24);
read_data_len <= s_axis_treq_tdata[43:36] ? ((s_axis_treq_tdata[43:36] + 1) >> 3) : DATA_LEN;//将读数据长度暂存;
end
else begin//其余时间拉低;
read_cmd <= 1'b0;
end
end
然后生成读响应(tresp通道)的数据有效指示信号和最后一个数据有效指示信号。
//发送带数据的响应报文;
always@(posedge clk)begin
if(rst)begin//初始值为0;
m_axis_tresp_tvalid <= 1'b0;
end//当发送完一帧数据时拉低;
else if(m_axis_tresp_tvalid && m_axis_tresp_tready && m_axis_tresp_tlast)begin
m_axis_tresp_tvalid <= 1'b0;
end//当检测到读命令时拉高,开始发送读响应报文;
else if(read_cmd)begin
m_axis_tresp_tvalid <= 1'b1;
end
end
//生成一帧数据的最后一个数据有效指示信号;
always@(posedge clk)begin
if(rst)begin//初始值为0;
m_axis_tresp_tlast <= 1'b0;
end
else if(m_axis_tresp_tvalid && m_axis_tresp_tready && m_axis_tresp_tlast)begin
m_axis_tresp_tlast <= 1'b0;//读完数据时拉低;
end
else if(read_cnt[4:0] == read_data_len - 1)begin
m_axis_tresp_tlast <= 1'b1;//读出最后一个数据时拉高;
end
end
最后时生成读数据,为了方便测试,读请求的返回数据使用一组递增的数据替代,包头使用带返回数据的应答包。
由于本文篇幅比较长了,下文仿真SRIO工程,并且通过光纤回环上板测试。
如果对文章内容理解有疑惑或者对代码不理解,可以在评论区或者后台留言,看到后均会回复!
如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!您的支持是我更新的最大动力!将持续更新工程!