zynq ps pl 通信实战
pl端产生的数据信息发送给ps的过程中,踩了不少坑,记录一下;本文几乎没有理论的东西,大部分都是如何在软件上操作,代码是怎么样的,对新手非常友好;因此,大佬可以跳过了;推荐链接: 传送门。
一.起因
由于项目需要,pl产生的数据需要传给ps,同时pl端数据产生的模块经常变化;在检索了很多文章后,发下你都是通过将pl端数据产生模块打包成用户ip核,再将ip核在block design中添加进来然后手动或者自动连接线路的方式,完成pl 和 ps之间的互联,结构就像这个样子:
图中红色框框代表pl中我们项目中需要产生数据的模块,蓝色框框是axi_lite的接口;红色框框根据时钟自动累加输出16位数据给axi接口。
在实际操作下来之后,就发现对于需要经常变化数据产生方式的项目来说,频繁打包ip核是个很繁琐的事情,比如上午我需要每一个时钟数据增加一次,下午我每10个时钟数据增加一次。。。就要经历这些苦逼的流程:
- 写pl端模块
- 打包模块IP核
- 修改模板axi接口
- 在项目中新建block design互联模块和接口
修改:
- 修改pl的代码
- 打包IP核(打包的时候会把之前的block design打包进去嘛?有待考证)
- 重新互联
所以为了避免反复打包ip,我想到下面这种互联方式,虽然换汤不换药,但是希望能减少操作步骤。
二.不打包IP互联
思想就是把arm的软核以及axi接口封装成一个黑盒子,使用的时候就例化给黑盒子的wrapper.v(就像调用用户编写的的模块一样),效果如图:(请忽视clk和rst … )
1.编写PL数据产生模块
本模块为了方便理解,就尽可能的简单,完成一个时钟计数的功能,为了arm能不漏数据的观测到累加的过程,特意给计数降速,来500个时钟再给计数+1,具体代码如下:
`timescale 1ns / 1ps
module tx_data (
input clk,
input rst,
output reg [15:0] data_out
);
parameter integer times_of_clk = 500;
reg [15:0] data_ache;
always@(posedge clk or posedge rst)begin
if(rst == 1'b1)begin
data_out <= 16'b0;
data_ache <= 16'b0;
end
else if(data_ache == times_of_clk) begin
data_ache <= 16'b0;
data_out <= data_out + 1'b1;
end
else begin
data_ache <= data_ache + 1'b1;
end
end
endmodule
2. 修改axi接口模板
上图吧:
next完了就是这个
起个名字next,然后默认参数就能点击完成了:
有问道next steps的,我习惯选择 edit ip,立马就能改代码了;
在新弹出来的临时工程里面,找准位置添加代码:
input wire [15:0] data_in,
.data_in(data_in),
里面套的那个.v 文件也要增加input,名字我都取得一样的,就不再粘贴一遍了
我们打算利用slv_reg0
作为ps 和pl通信的缓冲区,因此屏蔽掉模板中slv_reg0
被赋值的操作代码,添加上我们的赋值操作:
//slv_reg0 <= slv_reg0;
slv_reg0 <= {16'b0,data_in};
这个模板里面的功能是,在axi slave
接收数据(也就是axi master 写数据,这个例程用不到)的时候,把对应的数据放到地址对应的寄存器slv_reg0
~ slv_reg3
中,我们在default代码段替换了slv_reg0
赋值逻辑,就可以一直将data_in的数据更新到寄存器中;
修改好代码,把packaging steps里面的每一项都按照提示点一下,变成绿色对勾,点击re-package ip
完成AXI接口的修改工作,提示关闭工程之后,就可以在ip catelog里面找到它了。
3.block design
按照自己的芯片情况添加好软核
添加自定义的AXI接口,搜一下刚才自己定义的名字
自动连接:
效果:
把datain接口make external
马上就好了!
给.bd文件套一层wrapper.v,如图操作:
观察一下生成的wrapper内容:
这个data_in_0就是axi伸出来的接口,接下来我们把 这个wrapper.v和 数据生产模块,用同一个top.v文件包含起来;
4.连接wrapper.v 和 tx_data.v
- 新建一个top.v的空文件;
- 复制wrapper.v文件中除了
data_in_0
之外的所有接口,名字也保持一致; - 用top文件包含wrapper和tx_data模块
代码重复地方相当多,有兴趣可以对比观察:
wrapper.v 文件详情:
`timescale 1 ps / 1 ps
// wrapper.v 文件详情:
module tx_data_system_wrapper
(DDR_addr,
DDR_ba,
DDR_cas_n,
DDR_ck_n,
DDR_ck_p,
DDR_cke,
DDR_cs_n,
DDR_dm,
DDR_dq,
DDR_dqs_n,
DDR_dqs_p,
DDR_odt,
DDR_ras_n,
DDR_reset_n,
DDR_we_n,
FIXED_IO_ddr_vrn,
FIXED_IO_ddr_vrp,
FIXED_IO_mio,
FIXED_IO_ps_clk,
FIXED_IO_ps_porb,
FIXED_IO_ps_srstb,
data_in_0);
inout [14:0]DDR_addr;
inout [2:0]DDR_ba;
inout DDR_cas_n;
inout DDR_ck_n;
inout DDR_ck_p;
inout DDR_cke;
inout DDR_cs_n;
inout [3:0]DDR_dm;
inout [31:0]DDR_dq;
inout [3:0]DDR_dqs_n;
inout [3:0]DDR_dqs_p;
inout DDR_odt;
inout DDR_ras_n;
inout DDR_reset_n;
inout DDR_we_n;
inout FIXED_IO_ddr_vrn;
inout FIXED_IO_ddr_vrp;
inout [53:0]FIXED_IO_mio;
inout FIXED_IO_ps_clk;
inout FIXED_IO_ps_porb;
inout FIXED_IO_ps_srstb;
input [15:0]data_in_0;
wire [14:0]DDR_addr;
wire [2:0]DDR_ba;
wire DDR_cas_n;
wire DDR_ck_n;
wire DDR_ck_p;
wire DDR_cke;
wire DDR_cs_n;
wire [3:0]DDR_dm;
wire [31:0]DDR_dq;
wire [3:0]DDR_dqs_n;
wire [3:0]DDR_dqs_p;
wire DDR_odt;
wire DDR_ras_n;
wire DDR_reset_n;
wire DDR_we_n;
wire FIXED_IO_ddr_vrn;
wire FIXED_IO_ddr_vrp;
wire [53:0]FIXED_IO_mio;
wire FIXED_IO_ps_clk;
wire FIXED_IO_ps_porb;
wire FIXED_IO_ps_srstb;
wire [15:0]data_in_0;
tx_data_system tx_data_system_i
(.DDR_addr(DDR_addr),
.DDR_ba(DDR_ba),
.DDR_cas_n(DDR_cas_n),
.DDR_ck_n(DDR_ck_n),
.DDR_ck_p(DDR_ck_p),
.DDR_cke(DDR_cke),
.DDR_cs_n(DDR_cs_n),
.DDR_dm(DDR_dm),
.DDR_dq(DDR_dq),
.DDR_dqs_n(DDR_dqs_n),
.DDR_dqs_p(DDR_dqs_p),
.DDR_odt(DDR_odt),
.DDR_ras_n(DDR_ras_n),
.DDR_reset_n(DDR_reset_n),
.DDR_we_n(DDR_we_n),
.FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn),
.FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),
.FIXED_IO_mio(FIXED_IO_mio),
.FIXED_IO_ps_clk(FIXED_IO_ps_clk),
.FIXED_IO_ps_porb(FIXED_IO_ps_porb),
.FIXED_IO_ps_srstb(FIXED_IO_ps_srstb),
.data_in_0(data_in_0));
endmodule
top.v文件详情:
`timescale 1ns / 1ps
// top.v文件详情:
module top (DDR_addr,
DDR_ba,
DDR_cas_n,
DDR_ck_n,
DDR_ck_p,
DDR_cke,
DDR_cs_n,
DDR_dm,
DDR_dq,
DDR_dqs_n,
DDR_dqs_p,
DDR_odt,
DDR_ras_n,
DDR_reset_n,
DDR_we_n,
FIXED_IO_ddr_vrn,
FIXED_IO_ddr_vrp,
FIXED_IO_mio,
FIXED_IO_ps_clk,
FIXED_IO_ps_porb,
FIXED_IO_ps_srstb
//data_in_0
);
inout [14:0]DDR_addr;
inout [2:0]DDR_ba;
inout DDR_cas_n;
inout DDR_ck_n;
inout DDR_ck_p;
inout DDR_cke;
inout DDR_cs_n;
inout [3:0]DDR_dm;
inout [31:0]DDR_dq;
inout [3:0]DDR_dqs_n;
inout [3:0]DDR_dqs_p;
inout DDR_odt;
inout DDR_ras_n;
inout DDR_reset_n;
inout DDR_we_n;
inout FIXED_IO_ddr_vrn;
inout FIXED_IO_ddr_vrp;
inout [53:0]FIXED_IO_mio;
inout FIXED_IO_ps_clk;
inout FIXED_IO_ps_porb;
inout FIXED_IO_ps_srstb;
// input [15:0]data_in_0;
wire [14:0]DDR_addr;
wire [2:0]DDR_ba;
wire DDR_cas_n;
wire DDR_ck_n;
wire DDR_ck_p;
wire DDR_cke;
wire DDR_cs_n;
wire [3:0]DDR_dm;
wire [31:0]DDR_dq;
wire [3:0]DDR_dqs_n;
wire [3:0]DDR_dqs_p;
wire DDR_odt;
wire DDR_ras_n;
wire DDR_reset_n;
wire DDR_we_n;
wire FIXED_IO_ddr_vrn;
wire FIXED_IO_ddr_vrp;
wire [53:0]FIXED_IO_mio;
wire FIXED_IO_ps_clk;
wire FIXED_IO_ps_porb;
wire FIXED_IO_ps_srstb;
wire [15:0]data_in_0;
tx_data dyqTx(
.clk(),
.rst(),
.data_out(data_in_0)
);
tx_data_system_wrapper yqduan
(.DDR_addr(DDR_addr),
.DDR_ba(DDR_ba),
.DDR_cas_n(DDR_cas_n),
.DDR_ck_n(DDR_ck_n),
.DDR_ck_p(DDR_ck_p),
.DDR_cke(DDR_cke),
.DDR_cs_n(DDR_cs_n),
.DDR_dm(DDR_dm),
.DDR_dq(DDR_dq),
.DDR_dqs_n(DDR_dqs_n),
.DDR_dqs_p(DDR_dqs_p),
.DDR_odt(DDR_odt),
.DDR_ras_n(DDR_ras_n),
.DDR_reset_n(DDR_reset_n),
.DDR_we_n(DDR_we_n),
.FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn),
.FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),
.FIXED_IO_mio(FIXED_IO_mio),
.FIXED_IO_ps_clk(FIXED_IO_ps_clk),
.FIXED_IO_ps_porb(FIXED_IO_ps_porb),
.FIXED_IO_ps_srstb(FIXED_IO_ps_srstb),
.data_in_0(data_in_0));
endmodule
完成之后文件的包含情况如图所示:
结尾
这个工程是个半成品,可以看出来 tx_data模块的rst 和 clk都留空了,这个可以直接用wrapper的时钟还有reset,但是wrapper软核需要的时钟是差分信号,因此使用的过程中还需要借助 clock相关的ip核;
因为再top文件里面复制了wrapper的接口,名字保持一致,所以不用担心软核需要的约束文件的问题(软核的的始终,复位,ddr);
终于不用再打包IP核了!