Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互

Xilinx官方参考文档:ug994-vivado-ip-subsystems.pdf
黑金教程:
cource_s1_ALINX_ZYNQ(AX7010_AX7020)开发平台基础教程V1.06(第十一章)
course_s2_ALINX ZYNQ开发平台SDK应用教程V2.02.pdf(第十章)

前面介绍了PS与PL通过DMA通信的基本测试,但是之前用的都是xilinx的官方的IP核,接口都是做好的,我们直接例化就好。而在实际应用中,我们更加关心用户逻辑怎么与PS间进行数据通信。下面就介绍在BD中添加用户逻辑的方式,以及在其过程中所遇到的问题

这里我们用户逻辑以UART接口为代表设计。PL端实现UART功能,在SDK里发送数据通过DMA给PL端的UART发送出去。在外部将UART的收发回环,将发送出去的数据再收回来,通过DMA在给PS。此外我们在PL的uart上封装层slip协议,用来实现在uart上进行帧操作。测试硬件框图如下:
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第1张图片
就是在之前的测试环境中将AXIS_DATA_FIFO换成了我们用户逻辑UART部分,其中FIFO_to_AXIS是用来实现FIFO接口与AXIS接口的转换模块。

建立相应的vivado工程,并添加用户逻辑代码(模块不一一分析了):
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第2张图片
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第3张图片
此时我们发现在用户逻辑部分还缺少FIFO IP核,我们用IP catalog增加相应的IP核,具体IP核的参数根据自己的需要进行定制。
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第4张图片
在BD当中可以通过add IP和add module的方式来增加用户逻辑,两者稍微有点区别。
Add IP需要将用户逻辑提前封装好成IP核,然后通过IP Catalog来添加;而Add module可直接Diagram里添加。不过我在用Add module时遇到一些问题还没解决,可能BD并不认可我们写的fifo_to_axis的AXIS接口(其实在Add ip方式下我们并没有更改fifo_to_axis的逻辑,待定位问题)。

Add module方式:
在Diagram里右键,选add module,在弹出的对话框选择你添加模块的顶层文件:
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第5张图片
添加后模块上会显示RTL的标志,然后手动连线:
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第6张图片
连线完成后,进行validate时报如下错误:
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第7张图片
然后尝试定位问题,不过目前还没定位到地方,带我稍微再研究研究看看,我们先采用Add IP的方式。

Add IP:
首先我们需要将用户逻辑封装成AXIS4接口IP,步骤可参考相关教程,不细说,只介绍和教程有区别的地方。
新建一个New IP,取名my_uart_ip。这里我们选2个AXIS接口,一主一从,然后位宽是32位(其实我们需要8位,但这里改不了,我们先不用管)
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第8张图片
然后我们Edit in IP Packager,添加所需的用户逻辑代码:
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第9张图片
和之前一样增加用户逻辑内部的fifo IP核,不介绍了。

然后我们修改my_uart_ip_v1_0.v模块,去掉系统生成的两个子模块,加上我们自己的fifo_to_axis模块。
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第10张图片
my_uart_ip_v1_0.v具体代码如下(这个地方其实也看出我们并没有修改fifo_to_axis的代码,只是将fifo_to_axis模块用系统封装成IP核了,理论上和add module的效果一样,不知道为什么add module的方式不行):

`timescale 1 ns / 1 ps

	module my_uart_ip_v1_0
	(
		// Users to add ports here
		input               clk_50M,	   //50M
        input               rst_n,    //low active        
        
        //uart
        output              rs232_tx,
        input               rs232_rx,
        
        //pl_master PL TO PS
        output  [7:0]               m00_axis_tdata,
        output  [0:0]               m00_axis_tkeep,
        output                      m00_axis_tlast,
        input                       m00_axis_tready,
        output                      m00_axis_tvalid,
        input                       m00_axis_aresetn,
        input                       m00_axis_aclk,
        
        // pl_slave PS TO PL
        input  [7:0]                s00_axis_tdata,
        input  [0:0]                s00_axis_tkeep,
        input                       s00_axis_tlast,
        output                      s00_axis_tready,
        input                       s00_axis_tvalid,
        input                       s00_axis_aresetn,
        input                       s00_axis_aclk
	);

	// Add user logic here
     fifo_to_axi fifo_to_axi_inst(
    .clk_50M(clk_50M),    //50M
    .rst_n(rst_n & s00_axis_aresetn & m00_axis_aresetn),    //low active        
    
    //uart
    .rs232_tx(rs232_tx),
    .rs232_rx(rs232_rx),
    
    //AXI stream slave interface
    .ps2pl_s_clk(s00_axis_aclk),
    .ps2pl_s_data(s00_axis_tdata),
    .ps2pl_s_keep(s00_axis_tkeep),
    .ps2pl_s_valid(s00_axis_tvalid),
    .ps2pl_s_last(s00_axis_tlast),
    .ps2pl_s_ready(s00_axis_tready),

    //AXI stream master interface
    .pl2ps_m_clk(m00_axis_aclk),
    .pl2ps_m_data(m00_axis_tdata),
    .pl2ps_m_keep(m00_axis_tkeep),
    .pl2ps_m_valid(m00_axis_tvalid),
    .pl2ps_m_last(m00_axis_tlast),
    .pl2ps_m_ready(m00_axis_tready)
    );
	// User logic ends

	endmodule

然后修改下IP的接口(可能有一部分信号没有分配为总线方式),这时我们可以右键Auto infer Interface Chooser,选择你需要的接口方式:
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第11张图片
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第12张图片
然后还要关联下相应的时钟:
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第13张图片
最后Re-package IP,这样我们的用户IP就定制好了。

这样我们在BD中通过IP Catalog添加我们定义好的IP,并进行相关的连线:
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第14张图片
因为加入用户逻辑了,所以我们需要为用户逻辑分配引脚,在黑金AX_7020增加如下约束内容:
set_property PACKAGE_PIN F17 [get_ports rs232_rx_1]
set_property PACKAGE_PIN F16 [get_ports rs232_tx_1]
set_property IOSTANDARD LVCMOS33 [get_ports rs232_rx_1]
set_property IOSTANDARD LVCMOS33 [get_ports rs232_tx_1]

另外我们可以通过netlist方式增加Debug信号(具有层级关系,好定位需要加的信号)
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第15张图片
生成bit文件后导出硬件,打开SDK,SDK里的驱动和上一章一样,没有做修改,所以不再介绍了。

因为开发板上PL没有接UART芯片,没法直接接串口,我们用FPGA的2个IO口模拟UART的收发,在开发板上用跳线将收发端回环(可以用示波器抓取UART的波形):
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第16张图片
运行SDK的程序,进行测试,测试结果如下:
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第17张图片
我们在debug里分析下fifo_to_axis的相关接口波形,看到PL端通过AXIS接口收过来数据后写到了对应得fifo中(另外一侧一样不细分析):
Zynq PS_PL间通信学习(二) PS与用户逻辑UART进行数据交互_第18张图片
到此我们完整的实现了在zynq中PS和PL(用户逻辑)数据互通。

在此附上fifo_to_axis接口转换的代码,因为比较简单就没有采用状态机来实现:

`timescale 1ns / 1ps

module fifo_to_axi(
    clk_50M,	//50M
    rst_n,    //low active        
    
    //uart
    rs232_tx,
    rs232_rx,
    
    //AXI stream slave interface
    ps2pl_s_clk,
    ps2pl_s_data,
    ps2pl_s_keep,
    ps2pl_s_valid,
    ps2pl_s_last,
    ps2pl_s_ready,
    
    //AXI stream master interface
    pl2ps_m_clk,
    pl2ps_m_data,
    pl2ps_m_keep,
    pl2ps_m_valid,
    pl2ps_m_last,
    pl2ps_m_ready
    );
    
    input               clk_50M;	//50M
    input               rst_n;    //low active        
    
    //uart
    output              rs232_tx;
    input               rs232_rx;
    
    //AXI stream slave interface
    input               ps2pl_s_clk;
    input   [7:0]       ps2pl_s_data;
    input               ps2pl_s_keep;
    input               ps2pl_s_valid;
    input               ps2pl_s_last;
    output              ps2pl_s_ready;
    
    //AXI stream master interface
    input               pl2ps_m_clk;
    output  [7:0]       pl2ps_m_data;
    output              pl2ps_m_keep;
    output              pl2ps_m_valid;
    output              pl2ps_m_last;
    input               pl2ps_m_ready;
    
    reg                 ps2pl_s_ready;
    reg                 pl2ps_m_valid;
    wire                pl2ps_m_last;
    
    wire                ps2pl_fifo_empty;
    wire                ps2pl_fifo_wrclk;
    reg                 ps2pl_fifo_wr;
    reg     [7:0]       ps2pl_fifo_din;
    wire                ps2pl_fifo_full;
    
    wire                pl2ps_fifo_empty;
    wire                pl2ps_fifo_rdclk;
    wire                pl2ps_fifo_rd;
    reg                 pl2ps_fifo_rd_i;
    wire    [7:0]       pl2ps_fifo_dout;
    wire                pl2ps_fifo_rdy;
    reg                 pl2ps_data_valid;
    
    assign ps2pl_fifo_wrclk = ps2pl_s_clk;
    
    always @(posedge ps2pl_s_clk or negedge rst_n)
    begin
        if(!rst_n)
            ps2pl_s_ready <= 1'b0;
        else if(ps2pl_fifo_empty)
            ps2pl_s_ready <= 1'b1;
        else if(ps2pl_s_last)
            ps2pl_s_ready <= 1'b0;
    end
    
    always @(posedge ps2pl_s_clk or negedge rst_n)
    begin
        if(!rst_n)
            ps2pl_fifo_wr <= 1'b0;
        else if(ps2pl_s_valid & ps2pl_s_ready & ~ps2pl_fifo_full)
            ps2pl_fifo_wr <= 1'b1;
        else
            ps2pl_fifo_wr <= 1'b0;
    end
    
    always @(posedge ps2pl_s_clk or negedge rst_n)
    begin
        if(!rst_n)
            ps2pl_fifo_din <= 8'h00;
        else
            ps2pl_fifo_din <= ps2pl_s_data;
    end
    
    assign pl2ps_fifo_rdclk = pl2ps_m_clk;
    
    always @(posedge pl2ps_m_clk or negedge rst_n)
    begin
        if(!rst_n)
            pl2ps_fifo_rd_i <= 1'b0;
        else if(pl2ps_fifo_empty | ~pl2ps_m_ready)
            pl2ps_fifo_rd_i <= 1'b0;
        else if(pl2ps_m_ready & pl2ps_fifo_rdy)
            pl2ps_fifo_rd_i <= 1'b1;
        
    end
    
    assign pl2ps_fifo_rd = pl2ps_fifo_rd_i & pl2ps_m_ready & ~pl2ps_fifo_empty;
    
    always @(posedge pl2ps_m_clk or negedge rst_n)
    begin
        if(!rst_n)
            pl2ps_data_valid <= 1'b0;
        else if(pl2ps_fifo_rd)
            pl2ps_data_valid <= 1'b1;
        else if(pl2ps_m_last) 
            pl2ps_data_valid <= 1'b0;
    end
    
    always @(posedge pl2ps_m_clk or negedge rst_n)
    begin
        if(!rst_n)
            pl2ps_m_valid <= 1'b0;
        else if(pl2ps_fifo_empty)
           pl2ps_m_valid <= 1'b0; 
        else if((pl2ps_fifo_rd) || (pl2ps_data_valid & pl2ps_m_ready))
            pl2ps_m_valid <= 1'b1;
        else
            pl2ps_m_valid <= 1'b0;
    end
  
    assign pl2ps_m_keep = pl2ps_m_valid;
    
    assign pl2ps_m_data = pl2ps_fifo_dout;
    
    assign pl2ps_m_last = pl2ps_fifo_rd_i & pl2ps_fifo_empty;
    
    uart_top pl_uart(
    .clkin(clk_50M),    //50M
    .rst_n(rst_n),        //low active        
    
    .rs232_tx(rs232_tx),
    .rs232_rx(rs232_rx),
    
    .rs232_rate(3'b100),
    
    .ps2uart_fifo_empty(ps2pl_fifo_empty),
    .ps2uart_fifo_wrclk(ps2pl_fifo_wrclk),
    .ps2uart_fifo_wr(ps2pl_fifo_wr),
    .ps2uart_fifo_din(ps2pl_fifo_din),
    .ps2uart_fifo_full(ps2pl_fifo_full),
    
    .uart2ps_fifo_empty(pl2ps_fifo_empty),
    .uart2ps_fifo_rdclk(pl2ps_fifo_rdclk),
    .uart2ps_fifo_rd(pl2ps_fifo_rd),
    .uart2ps_fifo_dout(pl2ps_fifo_dout),
    .uart2ps_fifo_rdy(pl2ps_fifo_rdy)
    );   
endmodule

总结:
本文主要介绍了ZYNQ设计中在BD中加入用户逻辑的方法和具体流程以及测试过程中遇到的一些问题,供以后回顾。
其中通过Add Module的方式加入用户逻辑遇到的错误还需进一步解决,如果有小伙伴解决了该问题也请交流经验。

你可能感兴趣的:(zynq)