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的数据更新到寄存器中;