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上进行帧操作。测试硬件框图如下:
就是在之前的测试环境中将AXIS_DATA_FIFO换成了我们用户逻辑UART部分,其中FIFO_to_AXIS是用来实现FIFO接口与AXIS接口的转换模块。
建立相应的vivado工程,并添加用户逻辑代码(模块不一一分析了):
此时我们发现在用户逻辑部分还缺少FIFO IP核,我们用IP catalog增加相应的IP核,具体IP核的参数根据自己的需要进行定制。
在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,在弹出的对话框选择你添加模块的顶层文件:
添加后模块上会显示RTL的标志,然后手动连线:
连线完成后,进行validate时报如下错误:
然后尝试定位问题,不过目前还没定位到地方,带我稍微再研究研究看看,我们先采用Add IP的方式。
Add IP:
首先我们需要将用户逻辑封装成AXIS4接口IP,步骤可参考相关教程,不细说,只介绍和教程有区别的地方。
新建一个New IP,取名my_uart_ip。这里我们选2个AXIS接口,一主一从,然后位宽是32位(其实我们需要8位,但这里改不了,我们先不用管)
然后我们Edit in IP Packager,添加所需的用户逻辑代码:
和之前一样增加用户逻辑内部的fifo IP核,不介绍了。
然后我们修改my_uart_ip_v1_0.v模块,去掉系统生成的两个子模块,加上我们自己的fifo_to_axis模块。
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,选择你需要的接口方式:
然后还要关联下相应的时钟:
最后Re-package IP,这样我们的用户IP就定制好了。
这样我们在BD中通过IP Catalog添加我们定义好的IP,并进行相关的连线:
因为加入用户逻辑了,所以我们需要为用户逻辑分配引脚,在黑金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信号(具有层级关系,好定位需要加的信号)
生成bit文件后导出硬件,打开SDK,SDK里的驱动和上一章一样,没有做修改,所以不再介绍了。
因为开发板上PL没有接UART芯片,没法直接接串口,我们用FPGA的2个IO口模拟UART的收发,在开发板上用跳线将收发端回环(可以用示波器抓取UART的波形):
运行SDK的程序,进行测试,测试结果如下:
我们在debug里分析下fifo_to_axis的相关接口波形,看到PL端通过AXIS接口收过来数据后写到了对应得fifo中(另外一侧一样不细分析):
到此我们完整的实现了在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的方式加入用户逻辑遇到的错误还需进一步解决,如果有小伙伴解决了该问题也请交流经验。